React文档(十八)最佳性能
在内部,React使用好几种聪明的技巧去最小化更新UI所需要的DOM操作。对于很多应用来说,使用React会使得构建用户界面非常之快而且不需要做太多专门的性能优化。虽然如此,还是有一些方法可以让你为React应用加速。
使用生产构建
如果你正在性能测试或者在你的应用里遇到性能测试问题,确保你测试时使用了压缩了的生产构建:
- 对于创建React应用,你需要运行npm run build然后遵循指令
- 对于单文件构建,我们提供生产环境.min.js的文件版本
- 对于模块管理,你需要设置NPDE_ENV=production
- 对于webpack,你需要添加这个到配置文件的plugins里
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin()
- 对于汇总,你需要在commonjs插件之前使用replace插件因此只在开发环境使用的模块就不会被导入。完整的设置例子请看这里。
plugins: [
require('rollup-plugin-replace')({
'process.env.NODE_ENV': JSON.stringify('production')
}),
require('rollup-plugin-commonjs')(),
// ...
]
开发的构建包括了额外的警告很有帮助但是由于额外的统计所以会让程序变慢。
使用chrome performance对组件进行性能分析
在开发模式下,你可以通过使用浏览器里的性能工具来显示组件的实例化,更新和销毁。举个例子:
在chrome浏览器里这样做:
- 加载你的应用,使地址url的查询字符串为?react_perf
- 打开chrome开发者工具的performance面板并且按下record
- 然后做一些你想要测试分析的动作。不要录制超过20秒否则chrome可能会挂起
- 停止录制
- React事件将会成组地出现在user timing标签下面
注意那些数字是相对的因此组件在生产环境下会渲染地更快。还有,这样可以帮助你意识到不相关的UI会错误的更新,还有UI更新的深度和频率。
如今的chrome,edge和IE浏览器支持这个特性,但是我们使用的标准user timing API因此我们希望更多的浏览器可以添加对它的支持。
避免重复渲染
React在渲染出的UI内部建立和维护了一个内层的实现方式。这个内部表示包含了从组建里返回的React元素。这个内部表示让React避免了不必要的创建和关联DOM节点,那样会使速度变慢。有时被提到为“虚拟DOM”,但是在React Native里它同样存在。
当一个组件的props或者state改变了,React通过比较新返回的元素和之前渲染的元素来决定是否一个DOM的更新是必要的。当两者不一样的时候,React会更新DOM。
在一些情况下,你的组件通过重写生命周期函数shouldComponentUpdate可以为程序加速,shouldComponentUpdate是在重新渲染的流程开始之前被触发。这个函数默认会返回true,委托React去更新:
shouldComponentUpdate(nextProps, nextState) {
return true;
}
如果你知道在某些情况下你的组件不需要更新,你可以在shouldComponentUpdate里返回false,来跳过整个渲染流程,包括对该组件和之后的内容调用render()方法。
shouldComponentUpdate应用
下面是组件的树状目录。对于每一个节点,SCU指明了shouldComponentUpdate返回了什么,而vDOMEq指明了是否已经渲染的React元素发生了变化。最终,圆圈的颜色表明了是否组件需要重新渲染。
自从shouldComponentUpdate在树的节点C2处返回了false,React不会试图渲染C2节点,因此在C4和C5节点上也不需要调用shouldComponentUpdate。
对于C1和C3节点,shouldComponentUpdate返回了true,因此React必须往下到叶子节点去检查它们。对于C6shouldComponentUpdate返回了true,自从元素已经发生改变React就必须更新DOM。
最有趣的情况是C8。React必须渲染这个组件,但是自从React元素返回的和之前渲染的一样,那就不必更新DOM。
注意React只是必须改变C6的DOM,这是不可避免的。对于C8,它通过比较跳出了更新,并且对于C2的子树和C7,甚至不需要比较因为shouldComponentUpdate返回了false,所以render就不会被调用。
例子
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
} shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
} render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
在这段代码里,shouldComponentUpdate检查了props.color和stete.count的值是否有变化。如果它们没有变化,那么组件就不更新。如果你的组件越复杂,你就可以对于props和state使用“表面对比”类似的模式来决定是否组件应该更新。这个模式很常见,React提供了一个帮助工具来实现这个逻辑,它继承自React.PureComponent。所以下面的代码用简单的方式实现了同样的事:
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
} render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
大多数情况,可以使用React.PureComponent取代你自己写的shouldComponentUpdate。它只会做一个浅比较,所以当一个props或者state以某种方式突变那么浅比较可能会错过这个变化。
这在复杂的数据结构时就会出现问题。举个例子,这么说吧你想要一个ListOfWords组件去渲染一个逗号隔开的单词表,它会有一个WordAdder父组件让你按一下按钮就在列表添加一个单词。下面的代码运行会出错:
class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
} class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
} handleClick() {
// This section is bad style and causes a bug
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
} render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}
问题就在于PureComponent会做一个简单的比较在新的和旧的this.props.words之间。自从WordAdder类里的handleClick方法里的words数组发生了改变,旧的和新的this.props.words的值会比较为相同的,即使数组中的单词真的发生了变化。ListOfWords因此就不会更新即使它拥有了新的单词。
不会突变的数据的力量
最简单的方法去避免这个问题就是避免去使用可能会突变的props或者state。举个例子,上面的handleClick方法可以使用concat重写:
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
handleClick() {
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
};
你也可以重写改变对象的代码为了避免这个突变,通过类似的方式。举个例子,我们有一个对象名字叫做colormap并且我们想写一个函数来改变colormap.right为'blue'。我们可以这样写:
function updateColorMap(colormap) {
colormap.right = 'blue';
}
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
updateColorMap现在返回一个新对象,而不是改变旧的对象。Object.assign在ES6中并且要求一个polyfill。
这里有一个js建议要添加对象扩展操作符使得不修改而更新对象更加简便:
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
如果你正在创建React App,Object.assign和扩展操作符语法都默认是可用的。
使用不可变的数据结构
- 不可变的:一旦创建,一个合集在其他时间点不能被改变。
- 执着的:新的合集可以通过前一个合集和一个改变来建立。原始的集合在新的集合建立后依然可用。
- 结构分享:新的合集尽可能多的使用和原始集合同样的结构来创建,减少复制到最低限度来提高性能。
不可变性使得追踪改变很简单。每个变化都会导致产生一个新的对象,因此我们只需检查索引对象是否改变。举个例子,在这段js代码中:
const x = { foo: "bar" };
const y = x;
y.foo = "baz";
x === y; // true
const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
x === y; // false
在这个例子中,自从改变了x一个新的引用返回,我们可以设想x被改变了。
另外两个可以帮助我们使用不可改变的数据的库是seamless-immutable和immutability-helper。
不可变的数据结构提供了方便的方式来追踪对象的变化,这就是我们需要的东西来实现shouldComponentUpdate。这样你就可以获得一个很好的性能提高。
React文档(十八)最佳性能的更多相关文章
- React文档(八)条件渲染
在React中,你可以创建不同的组件各自封装你需要的东西.之后你可以只渲染其中的一部分,这取决于应用的state(状态). 条件渲染在React里就和js里的条件语句一样.使用js里的if或者条件表达 ...
- React文档(十三)思考React
在我们的看来,React是使用js创建大型快速网站应用的首要方法.它在Facebook和Instagram的使用已经为我们展现了它自己. React的一个很好的地方就在于当你创建应用的时候它使你思考如 ...
- React文档(二十四)高阶组件
高阶组件(HOC)是React里的高级技术为了应对重用组件的逻辑.HOCs本质上不是React API的一部分.它是从React的组合性质中显露出来的模式. 具体来说,一个高阶组件就是一个获取一个组件 ...
- react文档demo实现输入展示搜索结果列表
文档页面地址:https://doc.react-china.org/docs/thinking-in-react.html 该文档只给了具体实现思路,下面是我实现的代码. 初学react,如果有写的 ...
- React文档(一)安装
React是一个灵活的可以用于各种不同项目的框架,你可以用它来写新应用,你也可以逐步将它引进已有的代码库而不用重写整个项目. 试用React 如果你想玩一玩React,那么就去CodePen上试一试. ...
- [译]Selenium Python文档:八、附录:FAQ常见问题
另外一个FAQ:https://github.com/SeleniumHQ/selenium/wiki/Frequently-Asked-Questions 8.1.怎样使用ChromeDriver ...
- Drools文档(八) 规则语言参考
规则语言参考 概述 Drools有一个"本地"的规则语言.这种格式在标点符号上非常轻,并且通过"扩展器"支持自然语言和领域特定的语言,使语言能够变形到您的问题领 ...
- React文档(十六)refs和DOM
Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素. 在标准的React数据流中,props是使得父组件和子组件之间交互的唯一方式.你通过props重新 ...
- React文档(二十二)context
React中,通过React组件可以很容易地追踪数据流.当你关注一个组件,你可以发现哪一个props被传递了,这样使得你的应用很容被推断. 在一些情况下,你想要传递数据通过组件树而不需要去手动在每一层 ...
随机推荐
- python安装setup.py问题
一:python下setuptools安装( No module named setuptools 解决方案) 1. 下载:在它的官网可以下载到安装包:https://pypi.python.org/ ...
- jpa报错:Table 'dev-test.hibernate_sequence' doesn't exist
Hibernate 能够出色地自动生成主键.Hibernate/EBJ 3 注释也可以为主键的自动生成提供丰富的支持,允许实现各种策略.其生成规则由@GeneratedValue设定的.这里的@id和 ...
- 判断文件的编码 python
import chardet import string path1= r'C:\Users\25456\Desktop' path = path1 + r'\深度学习.txt' with open( ...
- Azure上搭建ActiveMQ集群-基于ZooKeeper配置ActiveMQ高可用性集群
ActiveMQ从5.9.0版本开始,集群实现方式取消了传统的Master-Slave方式,增加了基于ZooKeeper+LevelDB的实现方式. 本文主要介绍了在Windows环境下配置基于Zoo ...
- Android-Gradle(四)
当你在开发一个app,通常你会有几个版本.大多数情况是你需要一个开发版本,用来测试app和弄清它的质量,然后还需要一个生产版本.这些版本通常有不同的设置,例如不同的URL地址.更可能的是你可能需要一个 ...
- padding和margin
padding (内边距) 语法: (1)padding-left:10px; 左内边距 (2)padding-right:10px; 右内边距 (3)padding-top:10px; 上内边距 ( ...
- laravel5.7 migrate 时报错 Specified key was too long error 解决方案
今天在数据迁移时突然报了 Specified key was too long error 的错,解决掉之后就把这个问题记录下来. 报错原因 Laravel 5.4 + 默认使用 utf8mb4 字 ...
- Class打包成jar
Class打包成jar 现在我的文件夹的目录在: C:\Users\linsenq\Desktop\cglibjar 我要把位于这个目录下的所有文件夹以及这个文件夹下的.class文件打成jar包 第 ...
- 实际用到的linux小方法
2019.4.261.解决ssh端中文乱码 (1).查看系统(window)的字符集,在命令行界面顶端空白处,右键->属性->选项 底端查看即可. (2).ssh上查看系统支持的字符集 ...
- ES6 Promise用法讲解
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果. ES6 规定,Promise对象是一个构造函数,用来生成Promise实例. 下面代码创造了一个 ...