React之PureComponent

React避免重复渲染

React在渲染出的UI内部创建和维护了一个内层的实现方式,它包括了从组件返回的React元素。这种实现方式使得React避免了一些没必要要的建立和关联DOM节点,由于这样作可能比直接操做JavaScript对象更慢一些,它被称之为“虚拟DOM”。git

当一个组件的props或者state改变时,React经过比较新返回的元素和以前渲染的元素来决定是否有必要更新实际的DOM。当他们不相等时,React会更新DOM。github

在一些状况下,你的组件能够经过重写这个生命周期函数shouldComponentUpdate来提高速度, 它是在从新渲染过程开始前触发的。 这个函数默认返回true,可以使React执行更新:函数

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

举例

若是想让组件只在props.color或者state.count的值变化时从新渲染,你能够像下面这样设定shouldComponentUpdate性能

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

在以上代码中,shouldComponentUpdate只检查props.colorstate.count的变化。若是这些值没有变化,组件就不会更新。当你的组件变得更加复杂时,你可使用相似的模式来作一个“浅比较”,用来比较属性和值以断定是否须要更新组件。这种模式十分常见,所以React提供了一个辅助对象来实现这个逻辑 - 继承自React.PureComponent。如下代码能够更简单的实现相同的操做:this

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

PureComponent

原理

当组件更新时,若是组件的 props 和 state 都没发生改变, render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提高性能的目的。具体就是 React 自动帮咱们作了一层浅比较:code

if (this._compositeType === CompositeTypes.PureClass) {
    shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
}

而 shallowEqual 又作了什么呢?会比较 Object.keys(state | props) 的长度是否一致,每个 key 是否二者都有,而且是不是一个引用,也就是只比较了第一层的值,确实很浅,因此深层的嵌套数据是对比不出来的。对象

问题

大部分状况下,你可使用React.PureComponent而没必要写你本身的shouldComponentUpdate,它只作一个浅比较。可是因为浅比较会忽略属性或状态突变的状况,此时你不能使用它。继承

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中,this.props.words是WordAdder中传入的其state的一个引用。虽然在WordAdder的handelClick方法中被改变了,可是对于ListOfWords来讲,其引用是不变的,从而致使并无被更新。生命周期

解决方法

在上面的问题中能够发现,当一个数据是不变数据时,可使用一个引用。可是对于一个易变数据来讲,不能使用引用的方式给到PureComponent。简单来讲,就是咱们在PureComponent外层来修改其使用的数据时,应该给其赋值一个新的对象或者引用,从而才能确保其可以进行从新渲染。例如上面例子中的handleClick能够经过如下几种来进行修改从而确认正确的渲染:ip

handleClick() {
  this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
  }));
}

或者

handleClick() {
  this.setState(prevState => ({
    words: [...prevState.words, 'marklar'],
  }));
};

或者针对对象结构:

function updateColorMap(oldObj) {
  return Object.assign({}, oldObj, {key: new value});
}

immutable.js

Immutable.js是解决这个问题的另外一种方法。它经过结构共享提供不可突变的,持久的集合:

  • 不可突变:一旦建立,集合就不能在另外一个时间点改变。
  • 持久性:可使用原始集合和一个突变来建立新的集合。原始集合在新集合建立后仍然可用。
  • 结构共享:新集合尽量多的使用原始集合的结构来建立,以便将复制操做降至最少从而提高性能。
// 常见的js处理
const x = { foo: 'bar' };
const y = x;
y.foo = 'baz';
x === y; // true

// 使用 immutable.js

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

总结

PureComponent 真正起做用的,只是在一些纯展现组件上,复杂组件使用的话shallowEqual 那一关基本就过不了。另外在使用的过程当中为了确保可以正确的渲染,记得 propsstate 不能使用同一个引用哦。

相关文章
相关标签/搜索