React EffectList

React EffectList

EffectList

什么是 EffectList

一個(gè)由 Fiber 構(gòu)成的單向鏈表。
每個(gè) Fiber 節(jié)點(diǎn)都保存著自己子節(jié)點(diǎn)的 EffectList,F(xiàn)iber 對(duì)象上有三個(gè)指針:firstEffct、lastEffect片仿、nextEffect巷折,分別指向下一個(gè)待處理的 effect fiber甸赃,第一個(gè)和最后一個(gè)待處理的 effect fiber瀑罗。

為什么要有 EffectList

作為 DOM 操作的依據(jù),commit 階段需要找到所有有 effectTag 的 Fiber 節(jié)點(diǎn)并依次執(zhí)行 effectTag 對(duì)應(yīng)操作盾戴。難道需要在 commit 階段再遍歷一次 Fiber 樹(shù)尋找 effectTag !== null 的 Fiber 節(jié)點(diǎn)么寄锐?

這顯然是很低效的。

而 EffectList 就解決了這個(gè)問(wèn)題尖啡,在 Fiber 樹(shù)構(gòu)建過(guò)程中橄仆,每當(dāng)一個(gè) Fiber 節(jié)點(diǎn)的 effectTag 字段不為 NoEffect 時(shí)(代表需要執(zhí)行副作用),就把該 Fiber 節(jié)點(diǎn)添加到 EffectList衅斩,在 Fiber 樹(shù)構(gòu)建完成后沿癞,F(xiàn)iber 樹(shù)的 Effect List 也就構(gòu)建完成

EffectList 的收集

在 completeWork 的上層函數(shù) completeUnitOfWork 中,每個(gè)執(zhí)行完 completeWork 且存在 effectTag 的 Fiber 節(jié)點(diǎn)會(huì)被保存在一條被稱為 effectList 的單向鏈表中矛渴。effectList 中第一個(gè) Fiber 節(jié)點(diǎn)保存在 fiber.firstEffect椎扬,最后一個(gè)元素保存在 fiber.lastEffect。

Fiber 樹(shù)的構(gòu)建是深度優(yōu)先的具温,也就是先向下構(gòu)建子級(jí) Fiber 節(jié)點(diǎn)蚕涤,子級(jí)節(jié)點(diǎn)構(gòu)建完成后,再向上構(gòu)建父級(jí) Fiber 節(jié)點(diǎn)铣猩,所以 EffectList 中總是子級(jí) Fiber 節(jié)點(diǎn)在前面揖铜。

completeUnitOfWork 函數(shù)中所做的工作:

  • 完成該 fiber 節(jié)點(diǎn)的構(gòu)建

  • 將該 fiber 的 effectList 更新到其父 Fiber 節(jié)點(diǎn)上

  • 如果當(dāng)前節(jié)點(diǎn)有 effectTag,則將其加入 effectList

  • 如果有 sibling,移動(dòng)到 next sibling 進(jìn)行同樣的操作

  • 沒(méi)有 sibling 則返回父 fiber

function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork = unitOfWork;
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    let next = completeWork(current, completedWork, subtreeRenderLanes);

    // effect list構(gòu)建
    if (
      returnFiber !== null &&
      // Do not append effects to parents if a sibling failed to complete
      (returnFiber.effectTag & Incomplete) === NoEffect
    ) {
      // Append all the effects of the subtree and this fiber onto the effect
      // list of the parent. The completion order of the children affects the
      // side-effect order.
      if (returnFiber.firstEffect === null) {
        returnFiber.firstEffect = completedWork.firstEffect;
      }
      if (completedWork.lastEffect !== null) {
        if (returnFiber.lastEffect !== null) {
          returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
        }
        returnFiber.lastEffect = completedWork.lastEffect;
      }

      const effectTag = completedWork.effectTag;

      if (effectTag > PerformedWork) {
        if (returnFiber.lastEffect !== null) {
          returnFiber.lastEffect.nextEffect = completedWork;
        } else {
          returnFiber.firstEffect = completedWork;
        }
        returnFiber.lastEffect = completedWork;
      }
    }

    // 兄弟元素遍歷再到返返回父級(jí)
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      workInProgress = siblingFiber;
      return;
    }
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
}

看一個(gè)例子

<div id="1">
  <div id="4" />
  <div id="2">
    <div id="3" />
  </div>
</div>

最終形成的 EffectList 為

firstEffect => div4
lastEffect  => div1

因?yàn)?Fiber 樹(shù)的構(gòu)建深度優(yōu)先达皿,所以 div4 先完成 completeWork天吓,構(gòu)建 firstEffect。
EffectList 遍歷是從 firstEffect 開(kāi)始峦椰,通過(guò)每一個(gè)節(jié)點(diǎn)的 nextEffect 找到下一個(gè)節(jié)點(diǎn)龄寞。

firstEffect => div4
div4.nextEffect => div3
div3.nextEffect => div2
div2.nextEffect => div1

所以最終形成一條以 rootFiber.firstEffect 為起點(diǎn)的單向鏈表。

這樣汤功,在 commit 階段只需要遍歷 effectList 就能執(zhí)行所有 effect 了物邑。

                       nextEffect         nextEffect
rootFiber.firstEffect -----------> fiber -----------> fiber

EffectList 的遍歷

commit 階段就會(huì)從 rootFiber.firstEffect 開(kāi)始遍歷這個(gè) effectList 來(lái)執(zhí)行副作用

總結(jié)

在 beginWork 中我們知道有的節(jié)點(diǎn)被打上了 effectTag 的標(biāo)記,有的沒(méi)有滔金,而在 commit 階段時(shí)要遍歷所有包含 effectTag 的 Fiber 來(lái)執(zhí)行對(duì)應(yīng)的增刪改色解,那我們還需要從 Fiber 樹(shù)中找到這些帶 effectTag 的節(jié)點(diǎn)嘛,答案是不需要的餐茵,這里是以空間換時(shí)間科阎,在執(zhí)行 completeUnitOfWork 的時(shí)候遇到了帶 effectTag 的節(jié)點(diǎn),會(huì)將這個(gè)節(jié)點(diǎn)加入一個(gè)叫 effectList 中,所以在 commit 階段只要遍歷 effectList 就可以了(rootFiber.firstEffect.nextEffect 就可以訪問(wèn)帶 effectTag 的 Fiber 了)每個(gè) fiber 節(jié)點(diǎn)上都保存了該 fiber 節(jié)點(diǎn)的子節(jié)點(diǎn)的 effectList忿族,通過(guò) firstEffect锣笨、nextEffect刚梭、LastEffect 來(lái)保存,在 completeWork 的時(shí)候就會(huì)將每個(gè) fiber 的 effectList 更新到其父 Fiber 節(jié)點(diǎn)上票唆,所以 complete 之后,rootFiber 上就保存了完整的 effectList屹徘,我們?cè)?commit 階段就直接遍歷 rootFiber 上的 effectList 來(lái)執(zhí)行副作用即可

EffectList 不是全局變量走趋,只是在 Fiber 樹(shù)創(chuàng)建過(guò)程中,一層層向上收集有 effect 的 Fiber 節(jié)點(diǎn)噪伊,最終的 root 節(jié)點(diǎn)就會(huì)收集到所有有 effect 到 Fiber 節(jié)點(diǎn)簿煌,我們就把這條包含 effect 節(jié)點(diǎn)的鏈表叫做 EffectList。

由于收集的過(guò)程是深度優(yōu)先鉴吹,子級(jí)會(huì)先被收集姨伟,所以遍歷的時(shí)候也會(huì)先操作子級(jí),所以如果有面試官問(wèn)子級(jí)和父級(jí)的生命周期或者 useEffect 誰(shuí)先執(zhí)行豆励,就很清楚的知道會(huì)先執(zhí)行子級(jí)操作了夺荒。

補(bǔ)充

effectTag

effectTag

當(dāng) reconciler 工作結(jié)束后會(huì)通知 Renderer 需要執(zhí)行的 DOM 操作。要執(zhí)行 DOM 操作的具體類型就保存在 fiber.effectTag 中良蒸。

// DOM需要插入到頁(yè)面中
export const Placement = /*                */ 0b00000000000010;
// DOM需要更新
export const Update = /*                   */ 0b00000000000100;
// DOM需要插入到頁(yè)面中并更新
export const PlacementAndUpdate = /*       */ 0b00000000000110;
// DOM需要?jiǎng)h除
export const Deletion = /*                 */ 0b00000000001000;

初次 Render 時(shí)的 EffectList

在 React 中技扼,會(huì)對(duì)初次 Mount 有一個(gè)性能優(yōu)化,其中的 Fiber 節(jié)點(diǎn)的 effectTag 不會(huì)包含 placement嫩痰,對(duì)應(yīng)的 DOM 節(jié)點(diǎn)不會(huì)遍歷加入 DOM 樹(shù)剿吻,而是在創(chuàng)建 DOM 節(jié)點(diǎn)時(shí)就已經(jīng)加入 DOM 樹(shù)了,只有 rootFiber 節(jié)點(diǎn) FiberRootNode 的 effectTag 會(huì)包含 placement串纺。

EffectList 是不會(huì)包含 root 節(jié)點(diǎn)的丽旅,所以需要將 root 節(jié)點(diǎn)也添加到 EffectList,這樣才會(huì)正確的執(zhí)行 placement纺棺,讓 DOM 樹(shù)在頁(yè)面呈現(xiàn) 榄笙。

let firstEffect;
// 把根節(jié)點(diǎn)finishedWork也連接進(jìn)去
if (finishedWork.effectTag > PerformedWork) {
  if (finishedWork.lastEffect !== null) {
    finishedWork.lastEffect.nextEffect = finishedWork;
    firstEffect = finishedWork.firstEffect;
  } else {
    firstEffect = finishedWork;
  }
} else {
  // 根節(jié)點(diǎn)沒(méi)有effect.
  firstEffect = finishedWork.firstEffect;
}

那么,如果要通知 Renderer 將 Fiber 節(jié)點(diǎn)對(duì)應(yīng)的 DOM 節(jié)點(diǎn)插入頁(yè)面中祷蝌,需要滿足兩個(gè)條件:

  • fiber.stateNode 存在办斑,即 Fiber 節(jié)點(diǎn)中保存了對(duì)應(yīng)的 DOM 節(jié)點(diǎn)

  • (fiber.effectTag & Placement) !== 0,即 Fiber 節(jié)點(diǎn)存在 Placement effectTag

我們知道杆逗,mount 時(shí)乡翅,fiber.stateNode === null,且在reconcileChildren中調(diào)用的mountChildFibers不會(huì)為 Fiber 節(jié)點(diǎn)賦值 effectTag罪郊。那么首屏渲染如何完成呢蠕蚜?

針對(duì)第一個(gè)問(wèn)題,fiber.stateNode 會(huì)在 completeWork 中創(chuàng)建悔橄。

第二個(gè)問(wèn)題的答案十分巧妙:假設(shè) mountChildFibers 也會(huì)賦值 effectTag靶累,那么可以預(yù)見(jiàn) mount 時(shí)整棵 Fiber 樹(shù)所有節(jié)點(diǎn)都會(huì)有 Placement effectTag腺毫。那么 commit 階段在執(zhí)行 DOM 操作時(shí)每個(gè)節(jié)點(diǎn)都會(huì)執(zhí)行一次插入操作,這樣大量的 DOM 操作是極低效的挣柬。

為了解決這個(gè)問(wèn)題潮酒,在 mount 時(shí)只有 rootFiber 會(huì)賦值 Placement effectTag,在 commit 階段只會(huì)執(zhí)行一次插入操作邪蛔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末急黎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子侧到,更是在濱河造成了極大的恐慌勃教,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匠抗,死亡現(xiàn)場(chǎng)離奇詭異故源,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)汞贸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門绳军,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人矢腻,你說(shuō)我怎么就攤上這事删铃。” “怎么了踏堡?”我有些...
    開(kāi)封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵猎唁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我顷蟆,道長(zhǎng)诫隅,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任帐偎,我火速辦了婚禮逐纬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘削樊。我一直安慰自己豁生,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布漫贞。 她就那樣靜靜地躺著甸箱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪迅脐。 梳的紋絲不亂的頭發(fā)上芍殖,一...
    開(kāi)封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音谴蔑,去河邊找鬼豌骏。 笑死龟梦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的窃躲。 我是一名探鬼主播计贰,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蒂窒!你這毒婦竟也來(lái)了躁倒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤刘绣,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后挣输,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體纬凤,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年撩嚼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了停士。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡完丽,死狀恐怖恋技,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情逻族,我是刑警寧澤蜻底,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站聘鳞,受9級(jí)特大地震影響薄辅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抠璃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一站楚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搏嗡,春花似錦窿春、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至磅氨,卻和暖如春良蛮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悍赢。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工决瞳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留货徙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓皮胡,卻偏偏與公主長(zhǎng)得像痴颊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子屡贺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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