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'],
  }));
};