拆解setState[三][一源看世界][之React]

上一章節(jié)《拆解setState[二][一源看世界][之React]》講到了更新過(guò)程最核心的方法flushBatchedUpdates,那我們接著聊

flushBatchedUpdates方法的源碼中可以看出,它在ReactUpdatesFlushTransaction這個(gè)事務(wù)中執(zhí)行了runBatchedUpdates方法,源碼如下:

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
  ...

  // Since reconciling a component higher in the owner hierarchy usually (not
  // always -- see shouldComponentUpdate()) will reconcile children, reconcile
  // them before their children by sorting the array.
  dirtyComponents.sort(mountOrderComparator);

  // Any updates enqueued while reconciling must be performed after this entire
  // batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
  // C, B could update twice in a single batch if C's render enqueues an update
  // to B (since B would have already updated, we should skip it, and the only
  // way we can know to do so is by checking the batch counter).
  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    // If a component is unmounted before pending changes apply, it will still
    // be here, but we assume that it has cleared its _pendingCallbacks and
    // that performUpdateIfNecessary is a noop.
    var component = dirtyComponents[i];

    // If performUpdateIfNecessary happens to enqueue any new updates, we
    // shouldn't execute the callbacks until the next render happens, so
    // stash the callbacks first
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;

    var markerName;
    if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      // Duck type TopLevelWrapper. This is probably always true.
      if (
        component._currentElement.props ===
        component._renderedComponent._currentElement
      ) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: ' + namedComponent.getName();
      console.time(markerName);
    }

    ReactReconciler.performUpdateIfNecessary(
      component,
      transaction.reconcileTransaction,
      updateBatchNumber
    );

    if (markerName) {
      console.timeEnd(markerName);
    }

    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(
          callbacks[j],
          component.getPublicInstance()
        );
      }
    }
  }
}

這個(gè)方法遍歷所有的dirty components,通過(guò)mount order進(jìn)行排序(因?yàn)楦率菑母讣?jí)到子級(jí)),將所有setState的callback方法加入事務(wù)的隊(duì)列,運(yùn)行ReactReconcilerperformUpdateIfNecessary方法。所以我們得去看看ReactReconciler。


ReactReconciler & performUpdateIfNeeded - 最后的步驟了

直接看源碼實(shí)現(xiàn)吧

  performUpdateIfNecessary: function(
    internalInstance,
    transaction,
    updateBatchNumber
  ) {
    ...
    internalInstance.performUpdateIfNecessary(transaction);
    ...
  },

啊,原來(lái)是調(diào)用了internalInstance的方法,在上一章節(jié)中我們說(shuō)過(guò)internalInstanceReactCompositeComponentWrapper實(shí)例,它的prototype繼承了ReactCompositeComponent.Mixin,所以我們很容易就在ReactCompositeComponent找到了performUpdateIfNecessary這個(gè)方法,看下實(shí)現(xiàn)吧

  performUpdateIfNecessary: function(transaction) {
    if (this._pendingElement != null) {
      ReactReconciler.receiveComponent(
        this,
        this._pendingElement,
        transaction,
        this._context
      );
    } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
      this.updateComponent(
        transaction,
        this._currentElement,
        this._currentElement,
        this._context,
        this._context
      );
    } else {
      this._updateBatchNumber = null;
    }
  },

這個(gè)方法分為兩部分:

  • ReactReconciler.receiveComponent - 在element級(jí)別去比較components。所以element實(shí)例比較后,如果它們不一樣或者context改變了,就會(huì)觸發(fā)internal instance的receiveComponent
  • this.updateComponent將被調(diào)用,當(dāng)有pending state的情況

你可能在想有必要檢查pending state或者force updates嗎?state必須處于pending狀態(tài)那是因?yàn)槟阏{(diào)用了setState,對(duì)嗎?不是滴,updateComponent是遞歸的所有你可以有更新的組件,但pending state是空的。同時(shí)對(duì)_pendingElement的檢查是用于處理children被更新的場(chǎng)景。

updateComponent: function(
    transaction,
    prevParentElement,
    nextParentElement,
    prevUnmaskedContext,
    nextUnmaskedContext
  ) {
    var inst = this._instance;
    ...

    var willReceive = false;
    var nextContext;
    var nextProps;

    // Determine if the context has changed or not
    if (this._context === nextUnmaskedContext) {
      nextContext = inst.context;
    } else {
      nextContext = this._processContext(nextUnmaskedContext);
      willReceive = true;
    }

    nextProps = nextParentElement.props;

    // Not a simple state update but a props update
    if (prevParentElement !== nextParentElement) {
      willReceive = true;
    }

    // An update here will schedule an update but immediately set
    // _pendingStateQueue which will ensure that any state updates gets
    // immediately reconciled instead of waiting for the next batch.
    if (willReceive && inst.componentWillReceiveProps) {
      ...
      inst.componentWillReceiveProps(nextProps, nextContext);
      ...
    }

    var nextState = this._processPendingState(nextProps, nextContext);
    var shouldUpdate = true;

    if (!this._pendingForceUpdate && inst.shouldComponentUpdate) {
      ...
      shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
      ...
    }

    ...

    this._updateBatchNumber = null;
    if (shouldUpdate) {
      this._pendingForceUpdate = false;
      // Will set `this.props`, `this.state` and `this.context`.
      this._performComponentUpdate(
        nextParentElement,
        nextProps,
        nextState,
        nextContext,
        transaction,
        nextUnmaskedContext
      );
    } else {
      // If it's determined that a component should not update, we still want
      // to set props and state but we shortcut the rest of the update.
      this._currentElement = nextParentElement;
      this._context = nextUnmaskedContext;
      inst.props = nextProps;
      inst.state = nextState;
      inst.context = nextContext;
    }
  },

又是一個(gè)大方法!我們一步一步來(lái)解析它吧:

  • 首先,先對(duì)context進(jìn)行檢查。如果改變了,context會(huì)被存進(jìn)nextContext變量中。這里就不進(jìn)行展開(kāi)了。
  • updateComponent檢查props更新或者只是state更新。如果props更新,則觸發(fā)componentWillReceiveProps生命周期方法的執(zhí)行
  • 接下來(lái)是處理當(dāng)前最新的state。_processPendingState方法就是用來(lái)搞定這事的
  • 最后是判斷component是否應(yīng)該更新虛擬DOM,如果是,則通過(guò)_performComponentUpdate進(jìn)行更新。如果不是,則僅僅是更新變量的值

瞧一瞧_processPendingState

  _processPendingState: function(props, context) {
    var inst = this._instance;
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;

    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }

    var nextState = assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
      assign(
        nextState,
        typeof partial === 'function' ?
          partial.call(inst, nextState, props, context) :
          partial
      );
    }

    return nextState;
  },

可以看出設(shè)置和替換state共享同個(gè)隊(duì)列_pendingStateQueue,有一個(gè)屬性_pendingReplaceState用于判斷是否替換。如果是,pending state將合并replaced state;如果不是則合并當(dāng)前的state。

從源碼中也可以看出setState的第一個(gè)參數(shù)可以是個(gè)對(duì)象,也可以是一個(gè)函數(shù),通過(guò)這個(gè)函數(shù)的入?yún)⒖梢阅玫綄?shí)例,當(dāng)前最新的state, props和context,返回的是一個(gè)對(duì)象


ReactUpdates.asap

這是ReactUpdates的一個(gè)重要特性,在asap方法中實(shí)現(xiàn)

function asap(callback, context) {
  ...
  asapCallbackQueue.enqueue(callback, context);
  asapEnqueued = true;
}

它用在ReactUpdatesflushBatchedUpdates方法上,如:

if (asapEnqueued) {
  asapEnqueued = false;
  var queue = asapCallbackQueue;
  asapCallbackQueue = CallbackQueue.getPooled();
  queue.notifyAll();
  CallbackQueue.release(queue);
}

這個(gè)主要用在input elements上。一般情況調(diào)用callback的策略如下:所有的更新(包括嵌套的更新)完成后,Callbacks才被調(diào)用。Asap使callback可以在當(dāng)前的更新完成后立即調(diào)用 - 所以如果有嵌套的更新,必須等待asap callbacks完成后才能繼續(xù)


Wow,設(shè)置state真是一個(gè)好長(zhǎng)好長(zhǎng)的流程啊,有沒(méi)昏昏欲睡的感覺(jué),來(lái)個(gè)總結(jié)吧

  • 調(diào)用setState,它把pending state change和callbacks都丟給ReactUpdateQueue處理
  • ReactUpdateQueue更新component的internal instance,把所有更新存放到internal instance的隊(duì)列變量上,然后交給ReactUpdates
  • ReactUpdates利用batchingStrategy來(lái)保證所有state更新在一個(gè)事務(wù)中執(zhí)行和刷新
  • flushBatchedUpdates負(fù)責(zé)同步地原子性地進(jìn)行更新
  • ReactUpdatesFlushTransaction保證了嵌套的更新被正確地處理
  • runBatchedUpdates的職責(zé)是保證更新的順序,即從父級(jí)到子級(jí)的順序進(jìn)行更新,然后調(diào)用ReactReconciler來(lái)更新components
  • performUpdateIfNecessary的功能是判斷是prop還是state更新,然后調(diào)用updateComponent來(lái)處理更新
  • updateComponent區(qū)分更新的類(lèi)型,檢查shouldComponentUpdate的邏輯以判斷是否要阻止虛擬DOM的更新。同時(shí)觸發(fā)了一系列的生命周期方法(shouldComponentUpdate,componentWillReceiveProps,componentWillUpdate,componentDidUpdate
  • _processPendingState用于處理pending state并返回當(dāng)前最新的state對(duì)象值。它可以區(qū)分是部分設(shè)置還是整個(gè)替換,并且處理了第一個(gè)參數(shù)的不同類(lèi)型入?yún)⑦壿?object vs function)
  • 最后介紹了asap callbacks,用于解決輸入類(lèi)型組件更新state的問(wèn)題 - 它可以讓callback在組件更新后立即執(zhí)行,而不用等到所有嵌套組件完成更新才執(zhí)行

最后,期待吐槽,期待指教?。?!

--EOF--

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容