React源碼16.12.x --- Component 與 PureComponent

1. 一個React Component 實例中有些什麼?

基於實踐的真理,才經得起推敲,開始與混沌鬥爭的第一步:寫個例子看看。

import React from 'react';
class Test extends React.Component {
  state = {
    title: "空"
  };
  updateTitleHander = () => {
    this.setState({ title: "我是test" });
  }
  render() {
    const {title} = this.state;
    return (
      <div>
        <h1>{ title }</h1>
        <div onClick = {this.updateTitleHander} >
          Hello, 我是Test。
        </div>
      </div>
    );
  }
}
export default Test; 

斷點看看一個React Component實例到底什麼樣。可以看到這個實例有很多構造方法和屬性,並且原型鏈指向了Component,這,毫無疑問。
在這裏插入圖片描述

Component構造函數

主要維護props和context兩個對象,以及一個更新器updater。

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.
  this.updater = updater || ReactNoopUpdateQueue;
}

Component原型鏈方法包含setState方法和forceUpdate

原型鏈之setState

setState接受兩個參數partialState, callbackpartialState可以是對象,也可以是函數,callback將在state更新完畢後執行。

Component.prototype.setState = function (partialState, callback) {
  if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
    {
      throw Error("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');
};

原型鏈之forceUpdate

forceUpdate只接受一個回調函數。

Component.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

我們可以看到,以上兩個更新方法都用到了構造函數中的updater

updater是什麼

構造函數中有這樣一句話this.updater = updater || ReactNoopUpdateQueue;,先查看ReactNoopUpdateQueue,可以認爲是一個state的更新器,提供了強制跟新(立即更新)和 批量更新。,那麼這倆個方法都是通過調用warnNoop實現的,warnNoop又是什麼呢?==而且調用warnNoop時只傳遞了這個公共實例,並沒傳遞setState傳入的待更新的部分state。==問題先放這裏,繼續往下走。

var ReactNoopUpdateQueue = {
  // 檢查是否已掛載此複合組件
  isMounted: function (publicInstance) {
    return false;
  },
 // 強制更新。只有在確定   **不是**    在React代理的DOM事務中時,才應該調用這個函數。
  enqueueForceUpdate: function (publicInstance, callback, callerName) {
    warnNoop(publicInstance, 'forceUpdate');
  },
  
 // 替換所有狀態。 始終使用此方法或`setState`來改變state。 您應該將“ this.state”視爲不可變的。
 // 無法保證“this.state”會立即更新,因此調用此方法後訪問“this.state”可能會返回舊值。
  enqueueReplaceState: function (publicInstance, completeState, callback, callerName) {
    warnNoop(publicInstance, 'replaceState');
  },

  // 設置狀態的子集。 這僅是因爲_pendingState是內部的。
  // 這提供了合併策略,該合併策略不適用於深層屬性,這很令人困惑。
  // 暴露pendingState或在合併期間不要使用它。
  enqueueSetState: function (publicInstance, partialState, callback, callerName) {
    warnNoop(publicInstance, 'setState');
  }
};

但是!ReactNoopUpdateQueue只是未傳入updater時的備用選擇,在我進行單步調試的時候,實際上進入了react-dom.development.js下的classComponentUpdater.enqueueSetState。看看去,此次略去N秒,這classComponentUpdater提供的方法和ReactNoopUpdateQueue提供的一樣,但是方法內部的操作可就有意思了。先來看看enqueueSetState。

var classComponentUpdater = {
  isMounted: isMounted,
  enqueueSetState: function (inst, payload, callback) {},
  enqueueReplaceState: function (inst, payload, callback) { },
  enqueueForceUpdate: function (inst, callback) { }
};

classComponentUpdater.enqueueSetState

enqueueSetState接受三個參數,前面以及提到過了,inst是Component實例,payload是部分更新的state,callback還是那個更新結束後執行的那個callback。

  enqueueSetState: function (inst, payload, callback) {
    var fiber = get(inst);  // 基於實例,生成了一個FiberNode對象
    var currentTime = requestCurrentTimeForUpdate();
    var suspenseConfig = requestCurrentSuspenseConfig();
    var expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig); // 計算過期時間
    var update = createUpdate(expirationTime, suspenseConfig);  // 計算更新優先級
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      {
        warnOnInvalidCallback$1(callback, 'setState');
      }
      update.callback = callback;
    }
    enqueueUpdate(fiber, update); //創建更新隊列(createUpdateQueue),加到新隊列隊尾(appendUpdateToQueue),此時update中保存着待更新的部分state、過期時間及更新優先級等
    scheduleWork(fiber, expirationTime); // 生成fiberRootNode
  },

最後兩個方法涉及Fiber的運作原理,此次單步調試還看到了React的頂層事件代理機制,以及最後flushSyncCallbackQueue進行批量更新,內容衆多,將分次進行理解和記錄吧。

2. PureComponent是繼承而來嗎?

使用空函數原型鏈繼承方式,拷貝繼承了React Component的所有原型方法和屬性。徹底繼承,隔離了與Component的關係。PureComponent構造函數與Component構造函數簡直一般無二。特別的在原型鏈上添加了isPureReactComponent的屬性,用於後續更新階段的判斷。

function ComponentDummy() {}

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

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

_assign(pureComponentPrototype, Component.prototype); // _assign = require('object-assign'); // Object.assign() ponyfill

pureComponentPrototype.isPureReactComponent = true;

這裏簡單回顧下現有的幾種繼承方法,從而對比分析React純組件繼承普通組件的優勢何在。

PureComponent.prototype指向了一個名爲ComponentDummy的空函數作爲構造函數的實例,此時PureComponent.prototype可以繼承這個空函數原型鏈上的所有原型屬性和方法,然後使用Objec.assign將Component原型鏈是的方法和屬性都拷貝過來了,直接拷貝,和Component並不會產生任何繼承相關的副作用。

3. PureComponent的更新策略怎麼實現的?

React PureComponent 源碼解析
Component & PureComponent這兩個類基本相同,唯一的區別是PureComponent的原型上多了一個標識。
這是檢查組件是否需要更新的一個判斷,ctor就是你聲明的繼承自Component or PureComponent的類,他會判斷你是否繼承自PureComponent,如果是的話就shallowEqual比較state和props。
順便說一下:React中對比一個ClassComponent是否需要更新,只有兩個地方。一是看有沒有shouldComponentUpdate方法,二就是這裏的PureComponent判斷

react更新前做判斷是否更新的源碼

if (ctor.prototype && ctor.prototype.isPureReactComponent) { //判斷要不要進行淺比較
  return (
    !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
  );
}

看看淺比較的方法,基於Object.is()

function shallowEqual(objA, objB) {
// 判別基本類型異同
  if (is$1(objA, objB)) { // is$1 = Object.is() 判斷兩個值是否相同,與強等行爲基本一致
						  // 特別的Object.is(+0, -0) // false
						  // Object.is(NaN, NaN) // true
    return true;
  }
  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
    return false;
  }
 // 判別引用類型
  var keysA = Object.keys(objA); // 取鍵值數組
  var keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  } 

  for (var i = 0; i < keysA.length; i++) {
  // hasOwnProperty$2 = Object.prototype.hasOwnProperty
  // 檢測objB是否有keysA這個屬性, 再對這個屬性做Object.is比較,Object.is比較引用對象與強等一致,都是比較引用地址的
    if (!hasOwnProperty$2.call(objB, keysA[i]) || !is$1(objA[keysA[i]], objB[keysA[i]])) {
      return false;
    }
  }

  return true;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章