为何React和Immutable是好朋友

  目前工做中用到了React,搭配一块儿使用了Immutable.js。以前没有静下来思考一下为何React社区这么推崇搭配一块儿使用Immutable。正好想写篇文章分析一下这个问题。以前我翻译了React官方文档中Advanced Guides中关于一致化处理(Reconciliation)性能优化(Optimizing Performance)就涉及到这方面的内容。javascript

React性能优化

  一提到React,你们第一时间就想到的虚拟DOM(Virtual DOM)和伴随其带来的高性能。可是React提供的是声明式的API(declarative API),好的一方面是让咱们编写程序更加方便,但另外一方面,却使得咱们不太了解内部细节。java

一致化处理(Reconciliation)

  React采用的是虚拟DOM,每次属性(props)和状态(state)发生变化的时候,render函数返回不一样的元素树,React会检测当前返回的元素树和上次渲染的元素树以前的差别,而后找出何如高效的更新UI。react

react-tree

  上图展现的就是一个元素树,React比较两次元素树差别的时候,首先从根节点开始。若是元素类型不相同时,该节点如下(包括当前节点)的元素树都会被销毁,树的根节点如下的任何组件都会被卸载,所以状态(state)也会丢失。若是元素节点类型是相同的,那就须要区分是DOM元素仍是组件。若是是DOM元素,会保持节点相同,仅更新改变的属性。而若是是组件的话,会保持组件实例不变,仅更新组件实例的属性,所以组件实例的状态(state)就会被保留下来。比较完当前节点,而后会递归遍历比较子元素。git

  一致化处理(Reconciliation)包括的就是React元素的比较以及对应的React元素不一样时对DOM的更新,便可理解为React 内部将虚拟 DOM 同步更新到真实 DOM 的过程,包括新旧虚拟 DOM 的比较及计算最小 DOM 操做。咱们能够看到促使React性能提高的一个重要点就是避免一致化处理。github

shouldComponentUpdate

  React使用shouldComponentUpdate来判别组件是否会由于当前属性(props)和状态(state)变化而致使组件输出变化。默认的shouldComponentUpdate会在props和state发生变化时返回true,表示组件会从新渲染,从而调用render函数。固然了在首次渲染的时候和使用forceUpdate的时候,是不会通过shouldComponentUpdate判断。shouldComponentUpdate做为性能优化的一个很是有用且简单的方法,很是实用。
  性能优化

举例

  咱们以React官网中的图做为实例:ide

diff.png

  SCU表明shouldComponentUpdate,红色SCU表示shouldComponentUpdate返回true,绿色的SCU表示shouldComponentUpdate返回false。vDOMEq表明渲染的React元素是否相等。红色vDOMEq表示React元素不相等,绿色的vDOMEq表示React元素相等。元素节点为红色表示须要对该节点进行一致化处理,节点颜色为绿色表示不须要对其进行一致化处理。函数

  表示对于两棵元素树,React会同步比较。在比较C1节点,由于SCU返回的false,须要对其进行diff,vDOMEq返回的是false,故须要一致化处理,存在DOM元素的更新。迭代递归到C2,由于SCU返回的是true,以C2为根节点的整个子树,都不须要diff判断。可是C3的SCU返回true,须要进行diff比较。C3的子节点C6由于SCU返回true须要进行diff比较,而且由于vDOMEq返回的false,所以C6不可避免进行DOM的更新。对于C8来说,经过比较渲染元素而不须要进行一致化处理,而C7由于shouldComponentUpdate返回false从而不须要进行diff。性能

  所以咱们能够发现,若是可以合理地编写shouldComponentUpdate函数,从而能避免没必要要的一致化处理,使得性能能够极大提升。通常shouldComponentUpdate会比较propsstate中的属性是否发生改变(浅比较)来断定是否shouldComponentUpdate是否须要返回true从而触发一致化处理。咱们能够经过继承React.PureComponent或者经过引入PureRenderMixin模块来达到目的。可是这也存在一个问题:优化

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>
    );
  }
}

  组件展示一个以逗号分隔的单词列表,在父组件WordAdder,当你点击一个按钮时会给列表添加一个单词,但实际上,上面的代码是存在问题的,ListOfWords继承的React.PureComponent,当你每次点击按钮会给WordAdder组件中的this.state.words添加新的单词。所以ListOfWords中的shouldComponentUpdate在判断this.props.wordsnextProps.words实际是相等的,所以返回了false。因此,ListOfWords是不会被从新渲染的,由于React.PureComponent中的shouldComponentUpdate进行的是浅比较(shallow comparison),可是若是真的进行深比较,那么比较的性能损耗又太大,不由让咱们得出一个结论:

共享的可变状态是万恶之源

  这时候Immutable.js横空出世

Immutable Data

  Immutable Data是指一旦建立,就不能被更改的数据。对Immutable对象的修改都会返回新的Immutable对象。而且目前的Immutable库,都实现告终构共享,即若是对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享,避免了deepCopy把全部节点都复制一遍带来的性能损耗。比较两个Immutable对象是否相同,只须要使用===就能够轻松判别。所以若是React传入的数据是Immutable Data,那么React就能高效地比较先后属性的变化,从而决定shouldComponentUpdate的返回值。解决了上面存在的问题。  可是引入Immutable Data也不是没有代价的,毕竟Immutable Data须要引入新的API,而且须要引入新的库,在原有的项目中引入Immutable Data也是有风险和代价的并且还须要开发者转变原有的思惟(毕竟天下没有白吃的午饭)。

相关文章
相关标签/搜索