Component,PureComponent源碼解析

每次都信誓旦旦的給本身立下要好好學習react源碼的flag,結果都是由於某個地方卡住了,或是其餘緣由沒看多少就放棄了。此次又給本身立個flag-堅持看完 react源碼。爲了敦促本身,特開設這樣一個專欄來記錄本身的學習歷程,這意味着這個專欄的文章質量並不高,你能夠拿來參考參考,切莫全信,我不想誤人子弟,後面要是學有所成再考慮產出些好點的文章。 要是發現文章中有什麼不當之處,歡迎批評交流。我看的源碼版本是 16.8.2。我是用在源碼加註釋的方法學習的,放在 github上

爲了看react源碼,我查找了很多資料,這裏推薦兩個參考資料,我的以爲寫得不錯。javascript

  1. 慕課網一個課的電子書,他有個源碼解析的視頻教程,應該不錯,不過我沒買。
  2. 一個知乎專欄,寫得很清晰,只不過是15.6.2的, 在react16裏面一些方法找不到了。

Component, PureComponent是咱們最經常使用的東西,咱們常常繼承他們來建立組件。所以,我選擇從這幾個最最經常使用的東西入手開始欣賞React源碼。他們都位於packages/react目錄下,入口在index.js,index.js裏邊導出的實際上是src下的React.js裏的東西,在React.js中能夠看到React暴露的API。在React.js中能夠找到上面說述的Component,PureComponent和ReactElement相關線索。html

Component

Component和PureComponent都位於/packages/react/src/ReactBaseClasses.js。java

這兩個東西都是構造函數,或者稱爲類。react

Component的構造函數長成以下這樣:git

/**
 * Base class helpers for the updating state of a component.
 */

 // 常常去繼承他,原來這個構造行數是這樣的
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.

  // 這個new的時候須要注意updater是哪裏來的, 這個updater與setState應該有很大關係
  this.updater = updater || ReactNoopUpdateQueue;
}

這並無什麼神奇的,他接收三個參數,掛到this上。具體是這三個參數是啥,我目前也是不清楚的,由於咱們平時使用都是extends他而並無new他,new的過程應該是框架去作的,這個獲得後面再作分析。後面分析時須要注意updater,感受這裏會是一個重點,他有一個默認值,ReactNoopUpdateQueue,去看了下他的代碼,他是一個對象,掛了一些方法,這裏也就不展開了,我也沒太細看。github

Component的原型上掛了一些方法和屬性,isReactComponent屬性,setState方法,forceUpdate方法,代碼以下:api

// 一般isXxx都是boolean類型的,這裏比較奇怪,後面須要關注下
Component.prototype.isReactComponent = {};

/**
 * ...這裏有不少說明,能夠直接去看
 *
 * @param {object|function} partialState Next partial state or function to
 *        produce next partial state to be merged with current state.
 * @param {?function} callback Called after state is updated.
 * @final
 * @protected
 */

 // 原來咱們平時調用的setState就這麼幾行啊,可是看他是調用的updater的enqueueSetState,
 // 相關實現應該在那裏邊了, 能夠updater這個東西很厲害
Component.prototype.setState = function(partialState, callback) {
  // 這裏是個參數校驗,校驗不經過的話會給提示信息,並拋出異常
  invariant(
    typeof partialState === 'object' ||
      typeof partialState === 'function' ||
      partialState == null,
    'setState(...): takes an object of state variables to update or a ' +
      'function which returns an object of state variables.',
  );
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

/**
 * ...這裏有不少說明,能夠直接去看
 *
 * @param {?function} callback Called after update is complete.
 * @final
 * @protected
 */

 // 不多用到這個方法啊, 但他和setState同樣都是Component原型上的方法
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

其實Component的原型上掛載的東西也沒什麼神奇的,其中很是重要的是updater的enqueueSetState,enqueueForceUpdate方法,進一步說明了updater是後面分析的重點。框架

接下來的一段代碼是用來在開發模式下標記廢棄的api的,在開發模式下回給寫提示,代碼以下:函數

// 這裏是標識一些廢棄的api, 開發模式會報出來提醒開發這注意
if (__DEV__) {
  const deprecatedAPIs = {
    isMounted: [
      'isMounted',
      'Instead, make sure to clean up subscriptions and pending requests in ' +
        'componentWillUnmount to prevent memory leaks.',
    ],
    replaceState: [
      'replaceState',
      'Refactor your code to use setState instead (see ' +
        'https://github.com/facebook/react/issues/3236).',
    ],
  };
  const defineDeprecationWarning = function(methodName, info) {
    Object.defineProperty(Component.prototype, methodName, {
      get: function() {
        lowPriorityWarning(
          false,
          '%s(...) is deprecated in plain JavaScript React classes. %s',
          info[0],
          info[1],
        );
        return undefined;
      },
    });
  };
  for (const fnName in deprecatedAPIs) {
    if (deprecatedAPIs.hasOwnProperty(fnName)) {
      defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
    }
  }
}

__DEV__這個東西我沒找到是在哪裏掛到全局的(知道的同窗能夠留言指點下),可是看變量名能夠推測他是開發模式標識,這個提示咱們在作一些給別人用的東西時,接口協議約定十分重要,一旦約定就不能輕易變動,確實須要變動時須要通知調用方調整。回頭來,這裏標識廢棄了isMounted,replaceState兩個方法,其實他們被挪到了updater裏邊。oop

PureComponent

開始用React時老大Rewview個人代碼時常常寫評論,「你這個Component能夠改爲PureComponent」,當時一直不懂PureComponent與Component的區別(如今也沒全懂),只是聽人說PureComponent更新的時候是淺比較,而Component是深比較。今天看了這部分,其實也沒懂,不過感受後面再看看應該就懂了。要搞清這裏的PureComponet須要瞭解下js中繼承的實現,你們能夠參考《JavaScript高級程序設計》相關介紹,也能夠看看理解js繼承的6種方式, 筆者看到這個PureComponet也是先複習了下才看的。無論你看沒看, 代碼先貼出來:

// PureComponent

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;


// 發現PureComponnet的構造方法和Component是相同的
/**
 * Convenience component with default shallow equality check for sCU.
 */
function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.

// 感受不用加也能夠, 只不過會多查找一次,可是不得不說細節考慮的真棒

Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

我畫了個圖來理解這個繼承。

PureComponent的理解

首先是建立了一個ComponentDummy構造函數,他的原型指到Component的原型;而後建立了一個PureComponent, 加上了和Component同樣的屬性(這裏爲啥不用call)。PureComponent的原型指向ComponentDummy的實例;修改PureComponent原型的constructor屬性使其正確指向PureComponent的構造函數,並掛一個isPureReactComponent的屬性。爲了減小向上去查找原型鏈次數,用了一個assign直接將Component原型的東西拷貝到PureComponent的原型上(這裏仍是考慮的比較精細的)。

首先這個實現沒有啥問題,可是我有個疑問,你們能夠留言指點下:

爲何要用繼承,注意到PureComponent的構造函數和Component是同樣的,而後還有一個拷貝Component的原型到PureComponent的原型的操做,那這裏有繼承的必要嗎?不都是重寫的嗎,感受畫蛇添足。

下一篇預告 ReactElement源碼解析
相關文章
相關標籤/搜索