react优化性能

优化性能

Avoid Reconciliation

React创建并保存一渲染UI的镜像,通常被称为virtual DOM,当组件的props或者state发生变化时,React会通过对比虚拟DOM和新返回的元素,如果二者不同,则更新DOM。

在某些情况下,可以通过shouldComponentUpdate生命周期函数来加速这种对比的过程。此函数在重新渲染前触发,默认返回trueReact执行更新:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

如果你知道在哪种情况下,组件不需要更新,可以在此函数中返回false来跳过整个渲染的过程,包括调用本组件和以后组件中的render()

shouldComponentUpdate In Action

SCU代表shouldComponentUpdate返回值,绿色true红色false

vDOMEq 代表DOM是否完全相同,绿色相同红色不同 图片

C2节点shouldComponentUpdate返回falseReact不需要更新C2,甚至不用再去和C4 C5对比了。

C1 C3 SCU返回true,继续向下对比C6, SCU返回true,vDOMEq检测不相同,更新C6

虽然C3 SCU返回true,继续向下对比的时候C7跟以前渲染的DOM节点相同,所以不再更新

C8 对比DOM时相同,就不再更新了;而C2 C7时shouldComponentUpdate返回了false,所以甚至不用对比DOM,就可以直接跳过了,也不用调用render

示例 如果只有在props.colorstate.count变化时才更新组件

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

如果业务变的复杂了,这种逐一判断的方式就不太实用了,React提供了一种隐式对比的helper,继承自React.PureComponent,以上代码就可以简化为:

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

值得注意的是,propsstate如果以一种隐式判断不识别的方式写的时候,这种方法就不起作用了,比如有这样一个需求,父组件WordAdder调用子组件ListOfWords,每点击一次按钮,增加一个单词marklar,按通常思维有可能会按以下代码写

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

问题是PureComponent只会把this.props.words的新旧值做一个简单的对比。尽管数组wordshandleClick方法中发生了改变,但是覆盖了上一个生命周期中的state,所以在新旧对比的时候state是相同的,不会触发重新渲染。 因此就有了下面要说的不修改数据

The Power Of Not Mutating Data

最简单的方法就是不修改数据,上面示例中的handleClick可以重写为

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