通常,
React
通常会在渲染到视图中去的时候,会使用几种特殊的技术最小化要更新的DOM
节点从而实现性能优化,尽管在大部分的应用中,React
为了更快的引导用户使用,而没有做太多的优化工作,然而这些技术的使用性是必要的。
1.尽量避免更新
在React
建立和将内部的数据呈现给视图的过程中,React
做了如下的事情:通过在真实DOM
和React
应用之间建立一个过渡层,这个过渡层有时被称为”虚拟DOM“。虽然不是DOM
,但是有着和DOM
一样的机制处理,几乎所有的操作都会先在虚拟DOM
上进行处理,最后才更新到DOM
节点上的
当组件的属性或者状态发生改变时,React
会比较旧的DOM
和新的DOM
是不是不同,来确定更新当前DOM
节点的必要性,如果不同的话,React
就会更新这个DOM
节点
在我们写代码的过程中我们可以通过一个函数去控制当前的组件是否需要进行更新,什么时候一定要更新,什么时候不更新。
这个函数叫做shouldComponentUpdate
生命周期更新函数,这个函数会发生在重新render
视图渲染之前,此函数在React
中默认是返回true
,然后会执行render
函数进行更新。
shouldComponentUpdate(nextProps,nextState) {
return true;
}
如果你已经确定了在某一个种情况视图不需要更新,就可以返回false
,这会跳过render
函数和它的子组件的render
函数
2.shouldComponentUpdate
的更新行为
在下面这张图中,SCU
表示shouldComponentUpdate
的return
情况,vDOMEq
表示要更新到视图中的React
元素是否相等
在C2
之前shouldComponentUpdate
返回了false
,所以C2
包括C4
,C5
都不会进行更新
对于C1
,C3
,shouldComponentUpdate
返回了true
,所以React
会递归到更深的地方去检查他们是否需要更新,C6
时shouldComponentUpdate
返回了true
,并且旧元素和新元素不同即vDOMEq
为红色,所以一定会更新DOM
最后的C8
则是vDOMEq
为绿色,旧元素和新元素没有变化,所以不会更新
整颗树下来,React
更新的节点其实静仅仅是C6
,C3
之所有也更新都是因为内部元素C6
要更新,所以实际上我们需要更新的数据要少的很多,性能也就非常必要了
即使我们进行性能优化是非常有必要的,然而我们为了单单减少更新次数而增加我们的代码量是得不偿失的,所以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>
);
}
}
上面的代码就是自动进行优化了的。
当然这个父类有一点的缺陷,那就是它是引用比较,就是所谓的浅比较,对于对象和数组这些引用型数据就会出问题
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>
);
}
}
上面的功能是想一个数组中增加数据,而我们的底层组件ListOfWords
可以实时更新,然而在这份代码中是不起作用的,因为数组是引用型数据,我们比较数组==
号时,是相等的,所以不会进行更新,某种情况下,我们依旧需要直接重写shouldComponentUpdate
组件。
3.使用函数式编程规范
对于数据操作,请使用函数式编程规范,不影响原数据的值。如下
handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
上面这份代码中没有改变prevState
的值,不要试图用push
来处理,现在ES6
可以更好的支持函数式编程,比如说之前说到过的扩展运算符...
,也可以用在上面那个功能里用
handleClick() {
this.setState(prevState => ({
words: [...prevState.words,'marklar'],}));
};
比如说你可以直接复制改变某一个对象的值
function updateColorMap(colormap) {
colormap.right = 'blue';
}
function updateColorMap(colormap) {
return Object.assign({},colormap,{right: 'blue'});
}
Object.assign(ES6)
函数的用处是将对象colormap
中可以被for in
枚举的属性复制到{}
中并覆盖right
属性值为'blue'
。
这里也可以用扩展运算符
function updateColorMap(colormap) {
return {...colormap,right: 'blue'};
}
这个运算进行的是硬复制
4.尽可能的保持变量不变
即为常量,这里也是综合函数式编程的,将参数看成一个常量,我们唯有构造一个新的变量来处理,在我们之前大量的代码中反复用到的const
就是用于定义常量的,这是为了数据的安全性,和有效的控制数据变化
同时我们要尽可能让变量之间的共享性变小,比如说
const x = { foo: "bar" };
const y = x;
y.foo = "baz";
x === y; // true
上述代码中x
和y
共用了内存,进行了分享,隔离变量共享是为了有利我们的shouldComponentUpdate
函数处理,当我们用PureComponent
进行性能优化时,我们要消除引用型数据的干扰,进行变量共享分离是必要的,有一些插件可以非常快捷的实现这些功能,比如:Immutable.js
下一篇将讲
React
不用ES6
语法实现