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 不能使用同一個引用哦。

相關文章
相關標籤/搜索