拆解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)行展開了。
  • 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ū)分更新的類型坯癣,檢查shouldComponentUpdate的邏輯以判斷是否要阻止虛擬DOM的更新。同時(shí)觸發(fā)了一系列的生命周期方法(shouldComponentUpdate最欠,componentWillReceiveProps示罗,componentWillUpdatecomponentDidUpdate
  • _processPendingState用于處理pending state并返回當(dāng)前最新的state對(duì)象值芝硬。它可以區(qū)分是部分設(shè)置還是整個(gè)替換蚜点,并且處理了第一個(gè)參數(shù)的不同類型入?yún)⑦壿?object vs function)
  • 最后介紹了asap callbacks蛙酪,用于解決輸入類型組件更新state的問(wèn)題 - 它可以讓callback在組件更新后立即執(zhí)行贷币,而不用等到所有嵌套組件完成更新才執(zhí)行

最后元媚,期待吐槽贴届,期待指教@奚贰C胄菜枷!

--EOF--

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铃芦,一起剝皮案震驚了整個(gè)濱河市纤壁,隨后出現(xiàn)的幾起案子左刽,更是在濱河造成了極大的恐慌,老刑警劉巖酌媒,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欠痴,死亡現(xiàn)場(chǎng)離奇詭異迄靠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)喇辽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門梨水,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人茵臭,你說(shuō)我怎么就攤上這事疫诽。” “怎么了旦委?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵奇徒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我缨硝,道長(zhǎng)摩钙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任查辩,我火速辦了婚禮胖笛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宜岛。我一直安慰自己长踊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布萍倡。 她就那樣靜靜地躺著身弊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪列敲。 梳的紋絲不亂的頭發(fā)上阱佛,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音戴而,去河邊找鬼凑术。 笑死,一個(gè)胖子當(dāng)著我的面吹牛所意,可吹牛的內(nèi)容都是我干的淮逊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼扁眯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼壮莹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起姻檀,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤命满,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后绣版,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胶台,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡歼疮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诈唬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片韩脏。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖铸磅,靈堂內(nèi)的尸體忽然破棺而出赡矢,到底是詐尸還是另有隱情,我是刑警寧澤阅仔,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布吹散,位于F島的核電站,受9級(jí)特大地震影響八酒,放射性物質(zhì)發(fā)生泄漏空民。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一羞迷、第九天 我趴在偏房一處隱蔽的房頂上張望界轩。 院中可真熱鬧,春花似錦衔瓮、人聲如沸浊猾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)与殃。三九已至单山,卻和暖如春碍现,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背米奸。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工昼接, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悴晰。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓慢睡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親铡溪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子漂辐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容