React PureComponent 源碼解析

TL;DR

React 15.3.0 新增了一個 PureComponent 類,以 ES2015 class 的方式方便地定義純組件 (pure component)。這篇文章分析了一下源碼實現,並衍生探討了下 shallowComparePureRenderMixin。相關的 GitHub PR 在 這裏html

PureComponent 源碼分析

這個類的用法很簡單,若是你有些組件是純組件,那麼把繼承類從 Component 換成 PureComponent 便可。當組件更新時,若是組件的 propsstate 都沒發生改變,render 方法就不會觸發,省去 Virtual DOM 的生成和比對過程,達到提高性能的目的。react

import React, { PureComponent } from 'react'

class Example extends PureComponent {
  render() {
    // ...
  }
}

PureComponent 自身的源碼也很簡單,節選以下:git

function ReactPureComponent(props, context, updater) {
  // Duplicated from ReactComponent.
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

function ComponentDummy() {}
ComponentDummy.prototype = ReactComponent.prototype;
ReactPureComponent.prototype = new ComponentDummy();
ReactPureComponent.prototype.constructor = ReactPureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(ReactPureComponent.prototype, ReactComponent.prototype);
ReactPureComponent.prototype.isPureReactComponent = true;

上面的 ReactPureComponent 就是暴露給外部使用的 PureComponent 。能夠看到它只是繼承了 ReactComponent 再設定了一下 isPureReactComponent 屬性。ComponentDummy 是典型的 JavaScript 原型模擬繼承的作法,對此有疑惑的能夠看 個人另外一篇文章 。另外,爲了不原型鏈拉長致使方法查找的性能開銷,還用 Object.assign 把方法從 ReactComponent 拷貝過來了。github

PureRenderMixin 不同的是,這裏徹底沒有實現 shouldComponentUpdate。那 PureComponent 的 props/state 比對是在哪裏作的呢?答案是 ReactCompositeComponentsegmentfault

ReactCompositeComponent 這個類的信息太少,我只能推測它是負責實際渲染並維護組件實例的對象。建議你們從高層次瞭解 React 對組件的更新機制便可。如下幾篇官方文檔看完就足夠了。數組

這個類的代碼改動不少,但關鍵就在 這裏 。下面是我簡化後的代碼片斷:oop

// 定義 CompositeTypes
var CompositeTypes = {
  ImpureClass: 0,         // 繼承自 Component 的組件
  PureClass: 1,           // 繼承自 PureComponent 的組件
  StatelessFunctional: 2, // 函數組件
};

// 省略一堆代碼,由於加入了 CompositeTypes 形成的調整

// 這個變量用來控制組件是否須要更新
var shouldUpdate = true;

// inst 是組件實例
if (inst.shouldComponentUpdate) {
  shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
} else {
  if (this._compositeType === CompositeType.PureClass) {
    // 用 shallowEqual 對比 props 和 state 的改動
    // 若是都沒改變就不用更新
    shouldUpdate =
      !shallowEqual(prevProps, nextProps) ||
      !shallowEqual(inst.state, nextState);
  }
}

簡而言之,ReactCompositeComponent 會在 mount 的時候判斷各個組件的類型,設定 _compositeType ,而後根據這個類型來判斷是非須要更新組件。這個 PR 中大部分改動都是 由於加了 CompositeTypes 而作的調整性工做,實際跟 PureComponent 有關的就是 shallowEqual 的那兩行。

關於 PureComponent 的源碼分析就到這裏。其餘的就都是細節和測試,有興趣的能夠本身看看 PR 。

shallowEqual, shallowCompare, PureRenderMixin 的聯繫

咱們知道在 PureComponent 出現以前,shallowComparePureRenderMixin 均可以作同樣的事情。因而好奇看了一下後二者的代碼。

shallowCompare 的源碼:

var shallowEqual = require('shallowEqual');

function shallowCompare(instance, nextProps, nextState) {
  return (
    !shallowEqual(instance.props, nextProps) ||
    !shallowEqual(instance.state, nextState)
  );
}

module.exports = shallowCompare;

PureRenderMixin 的源碼:

var shallowCompare = require('shallowCompare');

var ReactComponentWithPureRenderMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  },
};

module.exports = ReactComponentWithPureRenderMixin;

能夠看到,shallowCompare 依賴 shallowEqual ,作的是跟剛纔在 ReactCompositeComponent 裏同樣的事情 -- 對比 props 和 state 。這個工具函數通常配合組件的 shouldComponentUpdate 一塊兒使用,而這就是 PureRenderMixin 作的事情。不過 PureRenderMixin 是配合 React.createClass 這種老的組件定義方式使用的,在 ES2015 class 裏用起來不是很方便,這也是 PureComponent 誕生的緣由之一。

最後 shallowEqual 這玩意定義在哪裏呢?它其實不是 React 的一部分,而是 fbjs 的一部分。這是 Facebook 內部使用的一個工具集。

小結

React 以前一直沒有針對 ES2015 class 的純組件寫法,雖然本身實現起來並不麻煩,但這也算給出了一個官方的解決方案,能夠再也不依賴 addon 了。不過 PureComponent 也不是萬能的,特定狀況下本身實現 shouldComponentUpdate 可能更高效。

參考資料

PureComponent PR
shallowEqual
Shallow Compare
PureRenderMixin

相關文章
相關標籤/搜索