react性能优化

2018-07-01  本文已影响23人  编程小飞侠

React 使用虚拟 DOM,它是在浏览器中的 DOM 子树的渲染描述,这个平行的描述让 React 避免创建和操作 DOM 节点,这些远比操作一个 JavaScript 对象慢。当一个组件的 props 或 state 改变,React 会构造一个新的虚拟 DOM 和旧的进行对比来决定真实 DOM 更新的必要性,只有在它们不相等的时候,React 才会使用尽量少的改动更新 DOM。

在此之上,React 提供了生命周期函数 shouldComponentUpdate,在重新渲染机制回路(虚拟 DOM 对比和 DOM 更新)之前会被触发,赋予开发者跳过这个过程的能力。这个函数默认返回 true,让 React 执行更新。

shouldComponentUpdate: function(nextProps, nextState) {
  return true;
}

一定要记住,React 会非常频繁的调用这个函数,所以要确保它的执行速度够快。

假如你有个带有多个对话的消息应用,如果只有一个对话发生改变,如果我们在 ChatThread 组件执行 shouldComponentUpdate,React 可以跳过其他对话的重新渲染步骤。

shouldComponentUpdate: function(nextProps, nextState) {
  // TODO: return whether or not current chat thread is
  // different to former one.
}

因此,总的说,React 通过让用户使用 shouldComponentUpdate 减短重新渲染回路,避免进行昂贵的更新 DOM 子树的操作,而且这些必要的更新,需要对比虚拟 DOM。

shouldComponentUpdate 实战

这里有个组件的子树,每一个都指明了 shouldComponentUpdate 返回值和虚拟 DOM 是否相等,最后,圆圈的颜色表示组件是否需要更新。

[图片上传失败...(image-225d59-1530447751850)]

在上面的示例中,因为 C2 的 shouldComponentUpdate 返回 false,React 就不需要生成新的虚拟 DOM,也就不需要更新 DOM,注意 React 甚至不需要调用 C4 和 C5 的 shouldComponentUpdate

C1 和 C3 的 shouldComponentUpdate 返回 true,所以 React 需要向下到叶子节点检查它们,C6 返回 true,因为虚拟 DOM 不相等,需要更新 DOM。最后感兴趣的是 C8,对于这个节点,React 需要计算虚拟 DOM,但是因为它和旧的相等,所以不需要更新 DOM。

注意 React 只需要对 C6 进行 DOM 转换,这是必须的。对于 C8,通过虚拟 DOM 的对比确定它是不需要的,C2 的子树和 C7,它们甚至不需要计算虚拟 DOM,因为 shouldComponentUpdate

那么,我们怎么实现 shouldComponentUpdate 呢?比如说你有一个组件仅仅渲染一个字符串:

React.createClass({
  propTypes: {
    value: React.PropTypes.string.isRequired
  },

  render: function() {
    return <div>{this.props.value}</div>;
  }
});

我们可以简单的实现 shouldComponentUpdate 如下:

shouldComponentUpdate: function(nextProps, nextState) {
  return this.props.value !== nextProps.value;
}

非常好!处理这样简单结构的 props/state 很简单,我门甚至可以归纳出一个基于浅对比的实现,然后把它 Mixin 到组件中。实际上 React 已经提供了这样的实现: PureRenderMixin

但是如果你的组件的 props 或者 state 是可变的数据结构呢?比如说,组件接收的 prop 不是一个像 'bar' 这样的字符串,而是一个包涵字符串的 JavaScript 对象,比如 { foo: 'bar' }:

React.createClass({
  propTypes: {
    value: React.PropTypes.object.isRequired
  },

  render: function() {
    return <div>{this.props.value.foo}</div>;
  }
});

前面的 shouldComponentUpdate 实现就不会一直和我们期望的一样工作:

// assume this.props.value is { foo: 'bar' }
// assume nextProps.value is { foo: 'bar' },
// but this reference is different to this.props.value
this.props.value !== nextProps.value; // true

这个问题是当 prop 没有改变的时候 shouldComponentUpdate 也会返回 true。为了解决这个问题,我们有了这个替代实现:

shouldComponentUpdate: function(nextProps, nextState) {
  return this.props.value.foo !== nextProps.value.foo;
}

基本上,我们结束了使用深度对比来确保改变的正确跟踪,这个方法在性能上的花费是很大的,因为我们需要为每个 model 写不同的深度对比代码。就算这样,如果我们没有处理好对象引用,它甚至不能工作,比如说这个父组件:

React.createClass({
  getInitialState: function() {
    return { value: { foo: 'bar' } };
  },

  onClick: function() {
    var value = this.state.value;
    value.foo += 'bar'; // ANTI-PATTERN!
    this.setState({ value: value });
  },

  render: function() {
    return (
      <div>
        <InnerComponent value={this.state.value} />
        <a onClick={this.onClick}>Click me</a>
      </div>
    );
  }
});

内部组件第一次渲染的时候,它会获取 { foo: 'bar' } 作为 value 的值。如果用户点击了 a 标签,父组件的 state 会更新成 { value: { foo: 'barbar' } },触发内部组件的重新渲染过程,内部组件会收到 { foo: 'barbar' } 作为 value 的新的值。

这里的问题是因为父组件和内部组件共享同一个对象的引用,当对象在 onClick 函数的第二行发生改变的时候,内部组件的属性也发生了改变,所以当重新渲染过程开始,shouldComponentUpdate 被调用的时候,this.props.value.foonextProps.value.foo 是相等的,因为实际上 this.props.valuenextProps.value 是同一个对象的引用。

因此,我们会丢失 prop 的改变,缩短重新渲染过程,UI 也不会从 'bar' 更新到 'barbar'

上一篇下一篇

猜你喜欢

热点阅读