React 性能优化

 2016年02月24日    1933     声明


React 会在任意时间点在状态改变后高效的重绘整个用户界面,其对虚拟DOM diff法保证了DOM的最小化重绘。在大多数情况下,React 对DOM的渲染效率可以满足我们需求。但少数情况下,我们需要更精细化的渲染来进一提高运行效率。本文介绍一些简单的优化方法,你可以在需要时参考使用。


  1. shouldComponentUpdate()方法
  2. shouldComponentUpdate()的使用
  3. 状态对比插件PureRenderMinix
  4. 不可变数据辅助插件update
  5. 性能分析
  6. 使用key标识


1. shouldComponentUpdate()方法

shouldComponentUpdate()方法是组件生命周期中的一个回调方法,我们可以在这个方法中判断组件是否需要更新,并最终决定是否要调用组件的render()方法。

React 组件会在propssate发生变化,或是在调用forceUpdate()方法后,React 会调用组件及其子组件的render()方法来重新渲染组件。有时候render()方法会在不必要的情况下被调用,这时重新渲染一个完全一样的虚拟DOM就是对性能的一种浪费。

这时,我们可以通过shouldComponentUpdate()方法来实现组件性能的进一步提升。shouldComponentUpdate()方法会返回一个布尔值,当返回false时,React 不会对组件重新渲染,而默认情况下shouldComponentUpdate()总是返回true。这样,我们就可以在shouldComponentUpdate()方法中判断组件是否需要重新渲染,以进一步提高组件的性能。


2. shouldComponentUpdate()的使用

shouldComponentUpdate语法如下:

boolean shouldComponentUpdate(object nextProps, object nextState)

shouldComponentUpdate会接受两个参数,即新的props和新的state,我们可以判断这两对象,并最终决定是否更新组件。

如,对于以下组件来说我们在shouldComponentUpdate中返回false,导致组件永远都不会更新:

var MyInput = React.createClass({
  getInitialState: function() {
    return {value: 'itbilu.com'};
  },
  handleChange: function(event) {
    this.setState({value: event.target.value});
  },
  shouldComponentUpdate: function(nextPorps, nextState) {
    return false;
  },
  render: function() {
    return <input type="text" value={this.state.value} onChange={this.handleChange} />;
  }
});

注意:shouldComponentUpdate()方法只会在组件的存在期被调用,在组件的初始化期并不会被调用。


3. 状态对比插件PureRenderMinix

propsstate相同时,所渲染出来的组件也会完全一样。我们可以通过React.addons.PureRenderMinix插件来处理shouldComponentUpdatePureRenderMinix插件会重写shouldComponentUpdate方法,并在方法内对比propsstate,如果新旧状态完全一样则返回false,这样就不会造成不必要的渲染,提高组件的运行效率。

var MyInput = React.createClass({
  mixin: [React.addons.PureRenderMinix],
  getInitialState: function() {
    return {value: 'itbilu.com'};
  },
  handleChange: function(event) {
    this.setState({value: event.target.value});
  },
  render: function() {
    return <input type="text" value={this.state.value} onChange={this.handleChange} />;
  }
});


4. 不可变数据辅助插件update

在使用PureRenderMinix插件对比propsstate时,如果propsstate结构较深或较复杂会导致对比过程比较慢。这时我们可以采用不可变的数据结构,React.addons.update可以帮助我们确保数据结构的不可变性,从而使shouldComponentUpdate方法的执行效率变的更高。

对于一个复杂的数据结构,如:

myData.x.y.z = 7;
// 或
myData.a.b.push(9);

我们不能直接对数据进行修改,因为之前的数据结构会被覆盖,这时我们会创建一个数据的副本以进行比较。这时,我们需要重用未改的对象,拷贝已改变的对象并设置新值。但是,用JavaScript来数据深层拷贝是非常困难的,这一过程可能会像下面这样:

var newData = extend(myData, {
  x: extend(myData.x, {
    y: extend(myData.x.y, {z: 7}),
  }),
  a: extend(myData.a, {b: myData.a.b.concat(9)})
});

React.addons.update参考了MongoDB的集合操作语法,让这一样过程变的简单。使用update插件,我们可以使用像下面这样来操作固定数据:

var newData = React.addons.update(myData, {
  x: {y: {z: {$set: 7}}},
  a: {b: {$push: [9]}}
});

通过React.addons.update插件复制不可变数据,并对可变数据进行对应的修改后,我们就可以shouldComponentUpdate()中对数据的新副本和对象进行比较,这样我们就实现了一个高效的shouldComponentUpdate(),进而提升应用的速度。

React.addons.update插件的详细用法,我们将在后面有单独的章节介绍。


5. 性能分析

合理的使用shouldComponentUpdate()可以在很大程序上优化应用。但在实际情况下,应用往往在沙箱或是开发环境中运行的非常快,但生产环境则表现的不尽人意。这时,我们需要对应用进行性能分析,然后再有针对性的在shouldComponentUpdate()中进行优化。

React 提供了性能分析插件React.addons.Perf,它让我们可以在需要检测的代码起始位置分别添加Perf.start()Perf.stop(),并可以通过Perf.printInclusive()方法打印花费时间,然后我们可以结合数据做进一步的分析。

React.addons.Perf插件的详细用法,我们将在后面有单独的章节介绍。


6. 使用key标识

key属性在组件类之外提供了另一种方式的组件标识。通过key标识我们可以组件如:顺序改变、不必要的子组件更新等情况下,告诉React 避免不必要的渲染而避免性能的浪费。

如,对于如一个基于排序的组件渲染:

var items = sortBy(this.state.sortingAlgorithm, this.props.items);
return items.map(function(item){
  return <img src={item.src} />
});

当顺序发生改变时,React 会对元素进行diff操作,并改img的src属性。显示,这样的操作效率是非常低的。这时,我们可以为组件添加一个key属性以唯一的标识组件:

return <img src={item.src} key={item.id} />

添加key属性后,React 会在diff算法中改变更新策略:不是更新组的src属性而是移动组件的位置。