React性能优化

当你们考虑在项目中使用 React 的时候,第一个问题每每是他们的应用的速度和响应是否能和非 React 版同样,每当状态改变的时候就从新渲染组件的整个子树,让你们怀疑这会不会对性能形成负面影响。React 用了一些黑科技来减小 UI 更新须要的花费较大的 DOM 操做。javascript

使用 production 版本

若是你在你的 React app 中进行性能测试或在寻找性能问题,必定要肯定你在使用 minified production build。开发者版本包括额外的警告信息,这对你在开发你的 app 的时候颇有用,可是由于要进行额外的处理,因此它也会比较慢。html

避免更新 DOM

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

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

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

必定要记住,React 会很是频繁的调用这个函数,因此要确保它的执行速度够快。git

假如你有个带有多个对话的消息应用,若是只有一个对话发生改变,若是咱们在 ChatThread 组件执行 shouldComponentUpdate,React 能够跳过其余对话的从新渲染步骤。github

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

所以,总的说,React 经过让用户使用 shouldComponentUpdate 减短从新渲染回路,避免进行昂贵的更新 DOM 子树的操做,并且这些必要的更新,须要对比虚拟 DOM。浏览器

shouldComponentUpdate 实战

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

should-component-update.png

在上面的示例中,由于 C2 的 shouldComponentUpdate 返回 false,React 就不须要生成新的虚拟 DOM,也就不须要更新 DOM,注意 React 甚至不须要调用 C4 和 C5 的 shouldComponentUpdate数据结构

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

注意 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'

Immutable-js 来救赎

Immutable-js 是 Lee Byron 写的 JavaScript 集合类型的库,最近被 Facebook 开源,它经过结构共享提供不可变持久化集合类型。一块儿看下这些特性的含义:

  • Immutable: 一旦建立,集合就不能再改变。

  • Persistent: 新的集合类型能够经过以前的集合建立,好比 set 产生改变的集合。建立新的集合以后源集合仍然有效。

  • Structural Sharing: 新的集合会使用尽可能多的源集合的结构,减小复制来节省空间和性能友好。若是新的集合和源集合相等,通常会返回源结构。

不可变让跟踪改变很是简单;每次改变都是产生新的对象,因此咱们仅须要对象的引用是否改变,好比这段简单的 JavaScript 代码:

var x = { foo: "bar" };
var y = x;
y.foo = "baz";
x === y; // true

尽管 y 被改变,由于它和 x 引用的是同一个对象,这个对比返回 true。然而,这个代码可使用 immutable-js 改写以下:

var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'bar'  });
var y = x.set('foo', 'baz');
x === y; // false

这个例子中,由于改变 x 的时候返回了新的引用,咱们就能够安全的认为 x 已经改变。

脏检测能够做为另外的可行的方式追踪改变,给 setters 一个标示。这个方法的问题是,它强制你使用 setters,并且要写不少额外的代码,影响你的类。或者你能够在改变以前深拷贝对象,而后进行深对比来肯定是否是发生了改变。这个方法的问题是,深拷贝和深对比都是很花性能的操做。

所以,不可变数据结构给你提供了一个高效、简洁的方式来跟踪对象的改变,而跟踪改变是实现 shouldComponentUpdate 的关键。因此,若是咱们使用 immutable-js 提供的抽象建立 props 和 state 模型,咱们就可使用 PureRenderMixin,并且可以得到很好的性能加强。

Immutable-js 和 Flux

若是你在使用 Flux,你应该开始使用 immutable-js 写你的 stores,看一下 full API

让咱们看一个可行的方式,使用不可变数据结构来给消息示例建立数据结构。首先咱们要给每一个要建模的实体定义一个 Record。Records 仅仅是一个不可变容器,里面保存一系列具体数据:

var User = Immutable.Record({
  id: undefined,
  name: undefined,
  email: undefined
});

var Message = Immutable.Record({
  timestamp: new Date(),
  sender: undefined,
  text: ''
});

Record 方法接收一个对象,来定义字段和对应的默认数据。

消息的 store 可使用两个 list 来跟踪 users 和 messages:

this.users = Immutable.List();
this.messages = Immutable.List();

实现函数处理每一个 payload 类型应该是比较简单的,好比,当 store 看到一个表明新消息的 payload 时,咱们就建立一个新的 record,并放入消息列表:

this.messages = this.messages.push(new Message({
  timestamp: payload.timestamp,
  sender: payload.sender,
  text: payload.text
});

注意:由于数据结构不可变,咱们须要把 push 方法的结果赋给 this.messages

在 React 里,若是咱们也使用 immutable-js 数据结构来保存组件的 state,我门能够把 PureRenderMixin 混入到我门全部的组件来缩短从新渲染回路。

这篇文章是翻译React官方文档