探索React源碼:Reconciler

探索React源碼:初探React fiber一文我們提到:

React16之后,React的架構(gòu)可以分成三層

  • Scheduler(調(diào)度)
  • Reconciler(協(xié)調(diào))
  • Renderer(渲染)

其中Reconciler(協(xié)調(diào)器)的作用是收集變化的組件,最終讓Renderer(渲染器)將變化的組件渲染的頁(yè)面當(dāng)中。這個(gè)收集變化的組件的過(guò)程我們稱為render(協(xié)調(diào))階段。在此階段,React會(huì)遍歷current fiber tree并將fiber節(jié)點(diǎn)與對(duì)應(yīng)的React element進(jìn)行對(duì)比(也就是我們常說(shuō)的diff),構(gòu)造出新的fiber tree —— workInProgress fiber tree。今天我們就來(lái)了解一下render階段的工作流程。

Reconciler起作用的階段我們稱為render階段,Renderer起作用的階段我們稱為commit階段

雙緩沖機(jī)制

雙緩存機(jī)制是一種在內(nèi)存中構(gòu)建并直接替換的技術(shù)。協(xié)調(diào)的過(guò)程中就使用了這種技術(shù)。

在React中同時(shí)存在著兩棵fiber tree。一棵是當(dāng)前在屏幕上顯示的dom對(duì)應(yīng)的fiber tree,稱為current fiber tree,而另一棵是當(dāng)觸發(fā)新的更新任務(wù)時(shí),React在內(nèi)存中構(gòu)建的fiber tree,稱為workInProgress fiber tree。

current fiber treeworkInProgress fiber tree中的fiber節(jié)點(diǎn)通過(guò)alternate屬性進(jìn)行連接。

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;

React應(yīng)用的根節(jié)點(diǎn)中也存在current屬性,利用current屬性在不同fiber tree的根節(jié)點(diǎn)之間進(jìn)行切換的操作,就能夠完成current fiber tree與workInProgress fiber tree之間的切換。

在協(xié)調(diào)階段,React利用diff算法,將產(chǎn)生update的React elementcurrent fiber tree中對(duì)應(yīng)的節(jié)點(diǎn)進(jìn)行比較,并最終在內(nèi)存中生成workInProgress fiber tree。隨后Renderer會(huì)依據(jù)workInProgress fiber tree將update渲染到頁(yè)面上。同時(shí)根節(jié)點(diǎn)的current屬性會(huì)指向workInProgress fiber tree,此時(shí)workInProgress fiber tree就變?yōu)閏urrent fiber tree。

fiber tree的遍歷流程

引入fiber后,fiber tree的遍歷過(guò)程:(不需要完全看懂,只需要看懂遍歷的流程就好)

// 執(zhí)行協(xié)調(diào)的循環(huán)
function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  //shouldYield為Scheduler提供的函數(shù), 通過(guò) shouldYield 返回的結(jié)果判斷當(dāng)前是否還有可執(zhí)行下一個(gè)工作單元的時(shí)間
  while (workInProgress !== null && !shouldYield()) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

function performUnitOfWork(unitOfWork: Fiber): void {
  //...

  let next;
  //...
  //對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行協(xié)調(diào),如果存在子節(jié)點(diǎn),則返回子節(jié)點(diǎn)的引用
  next = beginWork(current, unitOfWork, subtreeRenderLanes);

  //...

  //如果無(wú)子節(jié)點(diǎn),則代表當(dāng)前的child鏈表已經(jīng)遍歷完
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    //此函數(shù)內(nèi)部會(huì)幫我們找到下一個(gè)可執(zhí)行的節(jié)點(diǎn)
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  //...
}

function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork = unitOfWork;
  do {
    //...

    //查看當(dāng)前節(jié)點(diǎn)是否存在兄弟節(jié)點(diǎn)
    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      //若存在,便把siblingFiber節(jié)點(diǎn)作為下一個(gè)工作單元,繼續(xù)執(zhí)行performUnitOfWork,執(zhí)行當(dāng)前節(jié)點(diǎn)并嘗試遍歷當(dāng)前節(jié)點(diǎn)所在的child鏈表
      workInProgress = siblingFiber;
      return;
    }
    // Otherwise, return to the parent
    //如果不存在兄弟節(jié)點(diǎn),則回溯到父節(jié)點(diǎn),嘗試查找父節(jié)點(diǎn)的兄弟節(jié)點(diǎn)
    completedWork = returnFiber;
    // Update the next thing we're working on in case something throws.
    workInProgress = completedWork;
  } while (completedWork !== null);

  //...
}
未命名文件 (2).png

這個(gè)遍歷的過(guò)程實(shí)際上就是協(xié)調(diào)的整體過(guò)程,接下來(lái)我們來(lái)詳細(xì)看看在新的fiber節(jié)點(diǎn)是如何被創(chuàng)建的以及新的fiber樹是怎樣構(gòu)建出來(lái)的。

performSyncWorkOnRoot/performConcurrentWorkOnRoot

協(xié)調(diào)階段的入口為performSyncWorkOnRoot(legacy模式)或performConcurrentWorkOnRoot(concurrent 模式)。

// performSyncWorkOnRoot會(huì)調(diào)用該方法
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

// performConcurrentWorkOnRoot會(huì)調(diào)用該方法
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

這兩個(gè)方法會(huì)將生成workInProgress的下一級(jí)的fiber節(jié)點(diǎn),并將workInProgress的第一個(gè)子fiber節(jié)點(diǎn)賦值給workInProgress。新的workInProgress會(huì)與已創(chuàng)建的fiber節(jié)點(diǎn)連接起來(lái)構(gòu)成workInProgress fiber tree。

他們倆唯一的區(qū)別就是在判斷是否需要繼續(xù)遍歷時(shí),performConcurrentWorkOnRoot會(huì)在判斷是否存在下一工作單元workInProgress的基礎(chǔ)上,還會(huì)通過(guò)Scheduler模塊提供的shouldYield方法來(lái)詢問(wèn)當(dāng)前瀏覽器是否有充足的時(shí)間來(lái)執(zhí)行下一工作單元。

三種鏈表的遍歷

引入fiber前,React遍歷節(jié)點(diǎn)的方式是n叉樹的深度優(yōu)先遍歷,而引入fiber后,從fiber tree的遍歷過(guò)程我們能夠知道,React將遍歷的方法從原來(lái)的n叉樹的深度優(yōu)先遍歷改變?yōu)閷?duì)多種單向鏈表的遍歷:

  • 由 fiber.child 連接的父 -> 子鏈表的遍歷
  • 由 fiber.return 連接的子 -> 父鏈表的遍歷
  • 由 fiber.sibling 連接的兄 -> 弟鏈表的遍歷

這三種鏈表的遍歷主要通過(guò)beginWorkcompleteWork兩個(gè)方法進(jìn)行,我們來(lái)重點(diǎn)分析一下這兩個(gè)方法。

beginWork

beginWork的執(zhí)行路徑是workInProgress fiber tree中所有的父 -> 子鏈表。beginWork會(huì)根據(jù)傳入的fiber節(jié)點(diǎn)創(chuàng)建出當(dāng)前workInProgress fiber節(jié)點(diǎn)的所有次級(jí)workInProgress fiber節(jié)點(diǎn)(這些次級(jí)節(jié)點(diǎn)會(huì)通過(guò)fiber.sibling進(jìn)行連接),并將當(dāng)前workInProgress fiber節(jié)點(diǎn)于次級(jí)的第一個(gè)workInProgress fiber通過(guò)fiber.child屬性連接起來(lái)。

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // ...
}

beginWork接收三個(gè)參數(shù):

  • current:當(dāng)前組件在current fiber tree中對(duì)應(yīng)的fiber節(jié)點(diǎn),即workInProgress.alternate;
  • workInProgress:當(dāng)前組件在workInProgerss fiber tree中對(duì)應(yīng)的fiber節(jié)點(diǎn),即current.alternate
  • renderLanes:此次render的優(yōu)先級(jí);

我們知道,current fiber treeworkInProgress fiber tree中的fiber節(jié)點(diǎn)通過(guò)alternate屬性進(jìn)行連接的。

組件在mount時(shí),由于是首次渲染,workInProgress fiber tree中除了根節(jié)點(diǎn)fiberRootNode之外,其余節(jié)點(diǎn)都不存在上一次更新時(shí)的fiber節(jié)點(diǎn),也就是說(shuō),在mount時(shí),workInProgress fiber tree中除了根節(jié)點(diǎn)之外,所有節(jié)點(diǎn)的alternate都為空。所以在mount時(shí),除了根節(jié)點(diǎn)fiberRootNode之外,其余節(jié)點(diǎn)調(diào)用beginWork時(shí)參數(shù)current等于null

而update時(shí),workInProgress fiber tree所有節(jié)點(diǎn)都存在上一次更新時(shí)的fiber節(jié)點(diǎn),所以current !== null。

beginWork在mount和update時(shí)會(huì)分別執(zhí)行不同分支的工作。我們可以通過(guò) current === null 作為條件,判斷組件是處于mount還是update。隨后會(huì)根據(jù)當(dāng)前的workInProgress.tag的不同,進(jìn)入到不同的分支執(zhí)行創(chuàng)建子Fiber節(jié)點(diǎn)的操作。

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  //...

  if (current !== null) {
    //update時(shí)
    //...
  } else {
    //mount時(shí)
    didReceiveUpdate = false;
  }

  //...

  //根據(jù)tag不同,創(chuàng)建不同的子Fiber節(jié)點(diǎn)
  switch (workInProgress.tag) {
    case IndeterminateComponent: 
      // ...省略
    case LazyComponent: 
      // ...省略
    case FunctionComponent: 
      // ...省略
    case ClassComponent: 
      // ...省略
    case HostRoot:
      // ...省略
    case HostComponent:
      // ...省略
    case HostText:
      // ...省略
    // ...省略其他類型
  }
}

update時(shí)的beginWork

此時(shí)workInProgress存在對(duì)應(yīng)的current節(jié)點(diǎn),當(dāng)currentworkInProgress滿足一定條件時(shí),我們可以復(fù)用current節(jié)點(diǎn)的子節(jié)點(diǎn)的作為workInProgress的子節(jié)點(diǎn),反之則需要進(jìn)入對(duì)比(diff)的流程,根據(jù)比對(duì)的結(jié)果創(chuàng)建workInProgress的子節(jié)點(diǎn)。

beginWork在創(chuàng)建fiber節(jié)點(diǎn)的過(guò)程中中會(huì)依賴一個(gè)didReceiveUpdate變量來(lái)標(biāo)識(shí)當(dāng)前的current是否有更新。

在滿足下面的幾種情況時(shí),didReceiveUpdate === false:

  1. 未使用forceUpdate,且oldProps === newProps && workInProgress.type === current.type && !hasLegacyContextChanged() ,即props、fiber.type和context都未發(fā)生變化

  2. 未使用forceUpdate,且!includesSomeLane(renderLanes, updateLanes),即當(dāng)前fiber節(jié)點(diǎn)優(yōu)先級(jí)低于當(dāng)前更新的優(yōu)先級(jí)

const updateLanes = workInProgress.lanes;
if (current !== null) {
  //update時(shí)
  const oldProps = current.memoizedProps;
  const newProps = workInProgress.pendingProps;
  if (
    oldProps !== newProps ||
    hasLegacyContextChanged() ||
    (__DEV__ ? workInProgress.type !== current.type : false)
  ) {
    didReceiveUpdate = true;
  } else if (!includesSomeLane(renderLanes, updateLanes)) {
    // 本次的渲染優(yōu)先級(jí)renderLanes不包含fiber.lanes, 表明當(dāng)前fiber節(jié)點(diǎn)優(yōu)先級(jí)低于本次的渲染優(yōu)先級(jí),不需渲染
    didReceiveUpdate = false;
    //...
    // 雖然當(dāng)前節(jié)點(diǎn)不需要更新,但需要使用bailoutOnAlreadyFinishedWork循環(huán)檢測(cè)子節(jié)點(diǎn)是否需要更新
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  } else {
    if ((current.effectTag & ForceUpdateForLegacySuspense) !== NoEffect) {
      // forceUpdate產(chǎn)生的更新,需要強(qiáng)制渲染
      didReceiveUpdate = true;
    } else {
      didReceiveUpdate = false;
    }
  }
} else {
  //mount時(shí)
  //...
}

mount時(shí)的beginWork

由于在mount時(shí),直接將didReceiveUpdate賦值為false。

const updateLanes = workInProgress.lanes;
if (current !== null) {
  //update時(shí)
  //...
} else {
  //mount時(shí)
  didReceiveUpdate = false;
}

此處mount和update的不同主要體現(xiàn)在在didReceiveUpdate的賦值邏輯的不同, 后續(xù)進(jìn)入diff階段后,針對(duì)mount和update,diff的邏輯也會(huì)有所差別。

updateXXX

beginWork會(huì)根據(jù)當(dāng)前的workInProgress.tag的不同,進(jìn)入到不同的分支執(zhí)行創(chuàng)建子Fiber節(jié)點(diǎn)的操作。

switch (workInProgress.tag) {
  case IndeterminateComponent: 
    // ...
  case LazyComponent: 
    // ...
  case FunctionComponent: 
    // ...
  case ClassComponent: 
    // ...
  case HostRoot:
    // ...
  case HostComponent:
    // ...
  case HostText:
    // ...
  // ...
}

各個(gè)分支中的updateXXX函數(shù)的邏輯大致相同,主要經(jīng)歷了下面的幾個(gè)步驟:

  1. 計(jì)算當(dāng)前workInProgressfiber.memoizedState、fiber.memoizedProps等需要持久化的數(shù)據(jù);

  2. 獲取下級(jí)ReactElement對(duì)象,根據(jù)實(shí)際情況, 設(shè)置fiber.effectTag

  3. 根據(jù)ReactElement對(duì)象, 調(diào)用reconcilerChildren生成下級(jí)fiber子節(jié)點(diǎn),并將第一個(gè)子fiber節(jié)點(diǎn)賦值給workInProgress.child。同時(shí),根據(jù)實(shí)際情況, 設(shè)置fiber.effectTag;

我們以u(píng)pdateHostComponent為例進(jìn)行分析。HostComponent代表原生的 DOM 元素節(jié)點(diǎn)(如div,span,p等節(jié)點(diǎn)),這些節(jié)點(diǎn)的更新會(huì)進(jìn)入updateHostComponent。

function updateHostComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  //...

  //1. 狀態(tài)計(jì)算, 由于HostComponent是無(wú)狀態(tài)組件, 所以只需要收集 nextProps即可, 它沒(méi)有 memoizedState
  const type = workInProgress.type;
  const nextProps = workInProgress.pendingProps;
  const prevProps = current !== null ? current.memoizedProps : null;
  // 2. 獲取下級(jí)`ReactElement`對(duì)象
  let nextChildren = nextProps.children;
  const isDirectTextChild = shouldSetTextContent(type, nextProps);

  if (isDirectTextChild) {
    // 如果子節(jié)點(diǎn)只有一個(gè)文本節(jié)點(diǎn), 不用再創(chuàng)建一個(gè)HostText類型的fiber
    nextChildren = null;
  } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
  // 特殊操作需要設(shè)置fiber.effectTag 
    workInProgress.effectTag |= ContentReset;
  }
  // 特殊操作需要設(shè)置fiber.effectTag 
  markRef(current, workInProgress);
  // 3. 根據(jù)`ReactElement`對(duì)象, 調(diào)用`reconcilerChildren`生成`fiber`子節(jié)點(diǎn),并將第一個(gè)子fiber節(jié)點(diǎn)賦值給workInProgress.child。
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

在各個(gè)updateXXX函數(shù)中,會(huì)判斷當(dāng)前節(jié)點(diǎn)是否需要更新,如果不需要更新則會(huì)進(jìn)入bailoutOnAlreadyFinishedWork,并使用bailoutOnAlreadyFinishedWork的結(jié)果作為beginWork的返回值,提前beginWork,而不需要進(jìn)入diff階段。

常見的不需要更新的情況

  1. updateClassComponent時(shí)若!shouldUpdate && !didCaptureError
  2. updateFunctionComponent時(shí)若current !== null && !didReceiveUpdate
  3. updateMemoComponent時(shí)若compare(prevProps, nextProps) && current.ref === workInProgress.ref
  4. updateHostRoot時(shí)若nextChildren === prevChildren

bailoutOnAlreadyFinishedWork

bailoutOnAlreadyFinishedWork內(nèi)部先會(huì)判斷!includesSomeLane(renderLanes, workInProgress.childLanes)是否成立。

若!includesSomeLane(renderLanes, workInProgress.childLanes)成立,則所有的子節(jié)點(diǎn)都不需要更新,或更新的優(yōu)先級(jí)都低于當(dāng)前更新的渲染優(yōu)先級(jí)。此時(shí)以此節(jié)點(diǎn)為頭節(jié)點(diǎn)的整顆子樹都可以直接復(fù)用。此時(shí)會(huì)跳過(guò)整顆子樹,并使用null作為beginWork的返回值(進(jìn)入回溯的邏輯);

若不成立,則表示雖然當(dāng)前節(jié)點(diǎn)不需要更新,但當(dāng)前節(jié)點(diǎn)存在某些fiber子節(jié)點(diǎn)需要在此次渲染中進(jìn)行更新,則復(fù)用current fiber生成workInProgress的次級(jí)節(jié)點(diǎn);

function bailoutOnAlreadyFinishedWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  //...

  if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
    // renderLanes 不包含 workInProgress.childLanes
    // 所有的子節(jié)點(diǎn)都不需要在本次更新進(jìn)行更新操作,直接跳過(guò),進(jìn)行回溯
    return null;
  } 

  //...

  // 雖然此節(jié)點(diǎn)不需要更新,此節(jié)點(diǎn)的某些子節(jié)點(diǎn)需要更新,需要繼續(xù)進(jìn)行協(xié)調(diào)
  cloneChildFibers(current, workInProgress);
  return workInProgress.child;
}

effectTag

上面我們介紹到在updateXXX的主要邏輯中,在獲取下級(jí)ReactElement以及根據(jù)ReactElement對(duì)象, 調(diào)用reconcilerChildren生成fiber子節(jié)點(diǎn)時(shí),都會(huì)根據(jù)實(shí)際情況,進(jìn)行effectTag的設(shè)置。那么effrctTag的作用到底是什么呢?

我們知道,Reconciler的目的之一就是負(fù)責(zé)找出變化的組件,隨后通知Renderer需要執(zhí)行的DOM操作,effectTag正是用于保存要執(zhí)行DOM操作的具體類型的。
effectTag通過(guò)二進(jìn)制表示:

//...
// 意味著該Fiber節(jié)點(diǎn)對(duì)應(yīng)的DOM節(jié)點(diǎn)需要插入到頁(yè)面中。
export const Placement = /*                    */ 0b000000000000010;
//意味著該Fiber節(jié)點(diǎn)需要更新。
export const Update = /*                       */ 0b000000000000100;
export const PlacementAndUpdate = /*           */ 0b000000000000110;
//意味著該Fiber節(jié)點(diǎn)對(duì)應(yīng)的DOM節(jié)點(diǎn)需要從頁(yè)面中刪除。
export const Deletion = /*                     */ 0b000000000001000;
//...

通過(guò)這種方式保存effectTag可以方便的使用位操作為fiber賦值多個(gè)effect以及判斷當(dāng)前fiber是否存在某種effect。

React 的優(yōu)先級(jí) lane 模型中同樣使用了二進(jìn)制的方式來(lái)表示優(yōu)先級(jí)。

reconcileChildren

在各個(gè)updateXXX函數(shù)中,會(huì)根據(jù)獲取到的下級(jí)ReactElement對(duì)象, 調(diào)用reconcilerChildren生成當(dāng)前workInProgress fiber節(jié)點(diǎn)的下級(jí)fiber子節(jié)點(diǎn)。

在雙緩沖機(jī)制中我們介紹到:

在協(xié)調(diào)階段,React利用diff算法,將產(chǎn)生update的ReactElementcurrent fiber tree中對(duì)應(yīng)的節(jié)點(diǎn)進(jìn)行比較,并最終在內(nèi)存中生成workInProgress fiber tree。隨后Renderer會(huì)依據(jù)workInProgress fiber tree將update渲染到頁(yè)面上。同時(shí)根節(jié)點(diǎn)的current屬性會(huì)指向workInProgress fiber tree,此時(shí)workInProgress fiber tree就變?yōu)閏urrent fiber tree。

diff的過(guò)程就是在reconcileChildren中發(fā)生的。

本文的重點(diǎn)是Reconciler進(jìn)行協(xié)調(diào)的過(guò)程,我們只需要了解reconcileChildren函數(shù)的目的,不會(huì)對(duì)reconcileChildren中的diff算法的實(shí)現(xiàn)做更深入的了解,對(duì)React的diff算法感興趣的同學(xué)可閱讀探索React源碼:React Diff。

reconcileChildren也會(huì)通過(guò)current === null 區(qū)分mount與update,再分別執(zhí)行不同的工作:

export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes
) {
  if (current === null) {
    // 對(duì)于mount的組件
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    // 對(duì)于update的組件
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

mountChildFibersreconcileChildFibers的都是通過(guò)ChildReconciler生成的。他們的不同點(diǎn)在于shouldTrackSideEffects參數(shù)的不同,當(dāng)shouldTrackSideEffects為true時(shí)會(huì)為生成的fiber節(jié)點(diǎn)收集effectTag屬性,反之不會(huì)進(jìn)行收集effectTag屬性。

這樣做的目的是提升commit階段的效率。如果mountChildFibers也會(huì)賦值effectTag,由于mountChildFibers的節(jié)點(diǎn)都是首次渲染的,所以他們的effectTag都會(huì)收集到Placement effectTag。那么commit階段在執(zhí)行DOM操作時(shí),會(huì)導(dǎo)致每個(gè)fiber節(jié)點(diǎn)都需要進(jìn)行插入操作。為了解決這個(gè)問(wèn)題,在mount時(shí)只有根節(jié)點(diǎn)會(huì)進(jìn)行effectTag的收集,在commit階段只會(huì)執(zhí)行一次插入操作。

export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);

function ChildReconciler(shouldTrackSideEffects) {
  //...

  function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array<*>,
    lanes: Lanes,
  ): Fiber | null { 
    //... 
  }

  //...

  function reconcileSingleElement(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    element: ReactElement,
    lanes: Lanes,
  ): Fiber {
    //... 
  }

  //...

  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  ): Fiber | null {
    //... 
  }

  return reconcileChildFibers;
}

ChildReconciler內(nèi)部定義了許多用于操作fiber節(jié)點(diǎn)的函數(shù),并最終會(huì)使用一個(gè)名為 reconcileChildFibers 的函數(shù)作為返回值。這個(gè)函數(shù)的主要目的是生成當(dāng)前workInProgress fiber節(jié)點(diǎn)的下級(jí)fiber節(jié)點(diǎn),并將第一個(gè)子fiber節(jié)點(diǎn)作為本次beginWork返回值。

reconcileChildFibers的執(zhí)行過(guò)程中除了向下生成子節(jié)點(diǎn)之外,還會(huì)進(jìn)行下列的操作:

  1. 把即將要在commit階段中要對(duì)dom節(jié)點(diǎn)進(jìn)行的操作(如新增,移動(dòng): Placement, 刪除: Deletion)收集到effectTag中;
  2. 對(duì)于被刪除的fiber節(jié)點(diǎn), 除了節(jié)點(diǎn)自身的effectTag需要收集Deletion之外, 還要將其添加到父節(jié)點(diǎn)的effectList中(正常effectList的收集是在completeWork中進(jìn)行的, 但是被刪除的節(jié)點(diǎn)會(huì)脫離fiber樹, 無(wú)法進(jìn)入completeWork的流程, 所以在beginWork階段提前加入父節(jié)點(diǎn)的effectList)。

在遍歷的流程中我們可以看到,beginWork返回值不為空時(shí),會(huì)把該值賦值給workInProgress,作為下一次的工作單元,即完成了父 -> 子鏈表中的一個(gè)節(jié)點(diǎn)的遍歷。beginWork返回值為空時(shí)我們將進(jìn)入completeWork

completeUnitOfWork

當(dāng)beginWork返回值為空時(shí),代表在遍歷父->子鏈表的過(guò)程中發(fā)現(xiàn)當(dāng)前鏈表已經(jīng)無(wú)下一個(gè)節(jié)點(diǎn)了(也就是已遍歷完當(dāng)前父->子鏈表),此時(shí)會(huì)進(jìn)入到completeUnitOfWork函數(shù)。

completeUnitOfWork主要做了以下幾件事情:

  1. 調(diào)用completeWork。

  2. 用于進(jìn)行父節(jié)點(diǎn)的effectList的收集:

    • 把當(dāng)前 fiber 節(jié)點(diǎn)的 effectList 合并到父節(jié)點(diǎn)的effectList中。
    • 若當(dāng)前 fiber 節(jié)點(diǎn)存在存在副作用(增,刪,改), 則將其加入到父節(jié)點(diǎn)的effectList中。
  3. 沿著此節(jié)點(diǎn)所在的兄 -> 弟鏈表查看其是否擁有兄弟fiber節(jié)點(diǎn)(即fiber.sibling !== null),如果存在,則進(jìn)入其兄弟fiber父 -> 子鏈表的遍歷(即進(jìn)入其兄弟節(jié)點(diǎn)的beginWork階段)。如果不存在兄弟fiber,會(huì)通過(guò)子 -> 父鏈表回溯到父節(jié)點(diǎn)上,直到回溯到根節(jié)點(diǎn),也即完成本次協(xié)調(diào)。

function completeUnitOfWork(unitOfWork: Fiber): void {
  let completedWork = unitOfWork;
  // 此循環(huán)控制fiber節(jié)點(diǎn)向父節(jié)點(diǎn)回溯
  do {
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;
    if ((completedWork.flags & Incomplete) === NoFlags) {
      let next;
      //  使用completeWork處理Fiber節(jié)點(diǎn),后面再詳細(xì)分析completeWork
      next = completeWork(current, completedWork, subtreeRenderLanes); // 處理單個(gè)節(jié)點(diǎn)
      if (next !== null) {
        // Suspense類型的組件可能回派生出其他節(jié)點(diǎn), 此時(shí)回到`beginWork`階段進(jìn)行處理此節(jié)點(diǎn)
        workInProgress = next;
        return;
      }
      // 重置子節(jié)點(diǎn)的優(yōu)先級(jí)
      resetChildLanes(completedWork);
      if (
        returnFiber !== null &&
        (returnFiber.flags & Incomplete) === NoFlags
      ) {
        // 將此節(jié)點(diǎn)的effectList合并到到父節(jié)點(diǎn)的effectList中
        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;
        }
        // 若當(dāng)前 fiber 節(jié)點(diǎn)存在存在副作用(增,刪,改), 則將其加入到父節(jié)點(diǎn)的`effectList`中。
        const flags = completedWork.flags;
        if (flags > PerformedWork) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork;
          } else {
            returnFiber.firstEffect = completedWork;
          }
          returnFiber.lastEffect = completedWork;
        }
      }
    } else {
      // 異常處理
      //...
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // 如果有兄弟節(jié)點(diǎn), 則將兄弟節(jié)點(diǎn)作為下一個(gè)工作單元,進(jìn)入到兄弟節(jié)點(diǎn)的beginWork階段
      workInProgress = siblingFiber;
      return;
    }
    // 若不存在兄弟節(jié)點(diǎn),則回溯到父節(jié)點(diǎn)
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);
  // 已回溯到根節(jié)點(diǎn), 設(shè)置workInProgressRootExitStatus = RootCompleted
  if (workInProgressRootExitStatus === RootIncomplete) {
    workInProgressRootExitStatus = RootCompleted;
  }
}

completeWork

completeWork的作用包括:

  1. 為新增的 fiber 節(jié)點(diǎn)生成對(duì)應(yīng)的DOM節(jié)點(diǎn)。

  2. 更新DOM節(jié)點(diǎn)的屬性。

  3. 進(jìn)行事件綁定。

  4. 收集effectTag。

beginWork類似,completeWork針對(duì)不同fiber.tag也會(huì)進(jìn)入到不同的邏輯處理分支。

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      return null;
    case ClassComponent: {
      // ...
      return null;
    }
    case HostRoot: {
      // ...
      return null;
    }
    case HostComponent: {
      // ...
      return null;
    }
  // ...
}

我們繼續(xù)以HostComponent類型的節(jié)點(diǎn)為例,進(jìn)行分析。

在處理HostComponent時(shí),我們同樣需要區(qū)分當(dāng)前節(jié)點(diǎn)是需要進(jìn)行新建操作還是更新操作。但與beginWork階段判斷mount還是update不同的是,判斷節(jié)點(diǎn)是否需要更新時(shí),除了要滿足 current !== null 之外,我們還需要考慮workInProgress.stateNode節(jié)點(diǎn)是否為null,只有當(dāng)current !== null && workInProgress.stateNode != null時(shí),我們才會(huì)進(jìn)行更新操作。

個(gè)人猜測(cè),待驗(yàn)證:beginWork階段mount的節(jié)點(diǎn)的stateNode屬性為空,并且進(jìn)入到了completeWork階段才會(huì)被賦值。若在該節(jié)點(diǎn)進(jìn)入到beginWork階段之后,進(jìn)入到completeWork階段前的這段時(shí)間內(nèi),出現(xiàn)了更高優(yōu)先級(jí)的更新中斷了此次更新的情況,就有可能出現(xiàn)current !== null,但workInProgress.stateNode == null的情況,此時(shí)需要進(jìn)行新建操作。

更新時(shí)

進(jìn)入更新邏輯的fiber節(jié)點(diǎn)的stateNode屬性不為空,即已經(jīng)存在對(duì)應(yīng)的DOM節(jié)點(diǎn)。這時(shí)候我們只需要更新DOM節(jié)點(diǎn)的屬性并進(jìn)行相關(guān)effectTag的收集。

if (current !== null && workInProgress.stateNode != null) {
  updateHostComponent(
    current,
    workInProgress,
    type,
    newProps,
    rootContainerInstance,
  );

  // ref更新時(shí),收集Ref effectTag
  if (current.ref !== workInProgress.ref) {
    markRef(workInProgress);
  }
}

updateHostComponent

updateHostComponent用于更新DOM節(jié)點(diǎn)的屬性并在當(dāng)前節(jié)點(diǎn)存在更新屬性,收集Update effectTag。

updateHostComponent = function(
  current: Fiber,
  workInProgress: Fiber,
  type: Type,
  newProps: Props,
  rootContainerInstance: Container,
) {
  // props沒(méi)有變化,跳過(guò)對(duì)當(dāng)前節(jié)點(diǎn)的處理
  const oldProps = current.memoizedProps;
  if (oldProps === newProps) {
    return;
  }

  const instance: Instance = workInProgress.stateNode;
  const currentHostContext = getHostContext();

  // 計(jì)算需要變化的DOM節(jié)點(diǎn)屬性,并存儲(chǔ)到updatePayload 中,updatePayload 為一個(gè)偶數(shù)索引的值為變化的prop key,奇數(shù)索引的值為變化的prop value的數(shù)組。
  const updatePayload = prepareUpdate(
    instance,
    type,
    oldProps,
    newProps,
    rootContainerInstance,
    currentHostContext,
  );

  // 將updatePayload掛載到workInProgress.updateQueue上,供后續(xù)commit階段使用
  workInProgress.updateQueue = (updatePayload: any);
 
  // 若updatePayload不為空,即當(dāng)前節(jié)點(diǎn)存在更新屬性,收集Update effectTag
  if (updatePayload) {
    markUpdate(workInProgress);
  }
};

我們可以看到,需要變化的prop會(huì)被存儲(chǔ)到updatePayload 中,updatePayload 為一個(gè)偶數(shù)索引的值為變化的prop key,奇數(shù)索引的值為變化的prop value的數(shù)組。并最終掛載到掛載到workInProgress.updateQueue上,供后續(xù)commit階段使用。

prepareUpdate

prepareUpdate內(nèi)部會(huì)調(diào)用diff方法用于計(jì)算updatePayload。

export function prepareUpdate(
  instance: Instance,
  type: string,
  oldProps: Props,
  newProps: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): null | Object {
  const viewConfig = instance.canonical.viewConfig;
  const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);

  instance.canonical.currentProps = newProps;
  return updatePayload;
}

diff方法內(nèi)部實(shí)際是通過(guò)diffProperties方法實(shí)現(xiàn)的,diffProperties會(huì)對(duì)lastPropsnextProps進(jìn)行對(duì)比:

  1. 對(duì) input/option/select/textarea 的 lastProps & nextProps 做特殊處理,此處和React受控組件的相關(guān),不做展開。

  2. 遍歷 lastProps:

    • 當(dāng)遍歷到的prop屬性在 nextProps 中也存在時(shí),那么跳出本次循環(huán)(continue)。若遍歷到的prop屬性在 nextProps 中不存在,則進(jìn)入下一步。
    • 特殊處理style,判斷當(dāng)前prop是否為 style prop ,若不是,進(jìn)入下一步,若是,則將 style prop 整理到styleUpdates中,其中styleUpdates為以style prop的key值為key,''(空字符串)為value的對(duì)象,用于清空style屬性。
    • 由于進(jìn)入到此步驟的prop在 nextProps 中不存在,將此類型的prop整理進(jìn)updatePayload,并賦值為null,表示刪除此屬性。
  3. 遍歷 nextProps:

    • 當(dāng)遍歷到的prop屬性 與 lastProp 相等,即更新前后沒(méi)有發(fā)生變化,跳過(guò)。
    • 特殊處理style,判斷當(dāng)前prop是否為 style prop ,若不是,進(jìn)入下一步,若是,整理到 styleUpdates 變量中,其中styleUpdates為以style prop的key值為key,tyle prop的 value 為value的對(duì)象,用于更新style屬性。
    • 特殊處理 DANGEROUSLY_SET_INNER_HTML
    • 特殊處理 children
    • 若以上場(chǎng)景都沒(méi)命中,直接把 prop 的 key 和值都整理到updatePayload中。
  1. 若 styleUpdates 不為空,則將styleUpdates作為style prop 的值整理到updatePayload中。

新建時(shí)

進(jìn)入新建邏輯的fiber節(jié)點(diǎn)的stateNode屬性為空,不存在對(duì)應(yīng)的DOM節(jié)點(diǎn)。相比于更新操作,我們需要做更多的事情:

  1. 為 fiber 節(jié)點(diǎn)生成對(duì)應(yīng)的 DOM 節(jié)點(diǎn),并賦值給stateNode屬性。

  2. 將子孫DOM節(jié)點(diǎn)插入剛生成的DOM節(jié)點(diǎn)中。

  3. 處理 DOM 節(jié)點(diǎn)的所有屬性以及事件回調(diào)。

  4. 收集effectTag。

if (current !== null && workInProgress.stateNode != null) {
  // 更新操作
  // ...
} else {
    // 新建操作
    // 創(chuàng)建DOM節(jié)點(diǎn)
    const instance = createInstance(
      type,
      newProps,
      rootContainerInstance,
      currentHostContext,
      workInProgress,
    );

    // 將子孫DOM節(jié)點(diǎn)插入剛生成的DOM節(jié)點(diǎn)中
    appendAllChildren(instance, workInProgress, false, false);

    // 將DOM節(jié)點(diǎn)賦值給stateNode屬性
    workInProgress.stateNode = instance;

    // 處理 DOM 節(jié)點(diǎn)的所有屬性以及事件回調(diào)
    if (
      finalizeInitialChildren(
        instance,
        type,
        newProps,
        rootContainerInstance,
        currentHostContext,
      )
    ) {
      markUpdate(workInProgress);
    }
}

createInstance

createInstance負(fù)責(zé)給fiber節(jié)點(diǎn)生成對(duì)應(yīng)的DOM節(jié)點(diǎn)。

export function createInstance(
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): Instance {
  let parentNamespace: string;
  // ...

  // 創(chuàng)建 DOM 元素
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );

  // 在DOM節(jié)點(diǎn)中掛載一個(gè)指向 fiber 節(jié)點(diǎn)對(duì)象的指針
  precacheFiberNode(internalInstanceHandle, domElement);
  // 在 DOM節(jié)點(diǎn)中掛載一個(gè)指向 props 的指針
  updateFiberProps(domElement, props);
  return domElement;
}

appendAllChildren

appendAllChildren負(fù)責(zé)將子孫DOM節(jié)點(diǎn)插入剛生成的DOM節(jié)點(diǎn)中。

  appendAllChildren = function(
    parent: Instance,
    workInProgress: Fiber,
    needsVisibilityToggle: boolean,
    isHidden: boolean,
  ) {
    // 獲取workInProgress的子fiber節(jié)點(diǎn)
    let node = workInProgress.child;

    // 當(dāng)存在子節(jié)點(diǎn)時(shí),去往下遍歷
    while (node !== null) {
      if (node.tag === HostComponent || node.tag === HostText) {
        // 當(dāng)node節(jié)點(diǎn)為HostComponent后HostText時(shí),直接插入到子DOM節(jié)點(diǎn)列表的尾部
        appendInitialChild(parent, node.stateNode);
      } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
        appendInitialChild(parent, node.stateNode.instance);
      } else if (node.tag === HostPortal) {
        // 當(dāng)node節(jié)點(diǎn)為HostPortal類型的節(jié)點(diǎn),什么都不做
      } else if (node.child !== null) {
        // 上面分支都沒(méi)有命中,說(shuō)明node節(jié)點(diǎn)不存在對(duì)應(yīng)DOM,向下查找擁有stateNode屬性的子節(jié)點(diǎn)
        node.child.return = node;
        node = node.child;
        continue;
      }
      if (node === workInProgress) {
        // 回溯到workInProgress時(shí),以添加完所有子節(jié)點(diǎn)
        return;
      }

      // 當(dāng)node節(jié)點(diǎn)不存在兄弟節(jié)點(diǎn)時(shí),向上回溯
      while (node.sibling === null) {
        // 回溯到workInProgress時(shí),以添加完所有子節(jié)點(diǎn)
        if (node.return === null || node.return === workInProgress) {
          return;
        }
        node = node.return;
      }
      
      // 此時(shí)workInProgress的第一個(gè)子DOM節(jié)點(diǎn)已經(jīng)插入到進(jìn)入workInProgress對(duì)應(yīng)的DOM節(jié)點(diǎn)了,開始進(jìn)入node節(jié)點(diǎn)的兄弟節(jié)點(diǎn)的插入操作
      node.sibling.return = node.return;
      node = node.sibling;
    }
  };

  function appendInitialChild(parentInstance: Instance, child: Instance | TextInstance): void {
    parentInstance.appendChild(child);
  }

我們?cè)诮榻BbeginWork時(shí)介紹過(guò),在mount時(shí),為了避免每個(gè)fiber節(jié)點(diǎn)都需要進(jìn)行插入操作,在mount時(shí),只有根節(jié)點(diǎn)會(huì)收集effectTag,其余節(jié)點(diǎn)不會(huì)進(jìn)行effectTag的收集。由于每次執(zhí)行appendAllChildren后,我們都能得到一棵以當(dāng)前workInProgress為根節(jié)點(diǎn)的DOM樹。因此在commit階段我們只需要對(duì)mount的根節(jié)點(diǎn)進(jìn)行一次插入操作就可以了。

finalizeInitialChildren

function finalizeInitialChildren(
  domElement: Instance,
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): boolean {
  // 此方法會(huì)將 DOM 屬性掛載到 DOM 節(jié)點(diǎn)上,并進(jìn)行事件綁定
  setInitialProperties(domElement, type, props, rootContainerInstance);
  // 返回 props.autoFocus 的值
  return shouldAutoFocusHostComponent(type, props);
}

effectList

我們?cè)诮榻BcompleteUnitOfWork函數(shù)的時(shí)候提到,他的其中一個(gè)作用是用于進(jìn)行父節(jié)點(diǎn)的effectList的收集:
- 把當(dāng)前 fiber 節(jié)點(diǎn)的 effectList 合并到父節(jié)點(diǎn)的effectList中。
- 若當(dāng)前 fiber 節(jié)點(diǎn)存在存在副作用(增,刪,改), 則將其加入到父節(jié)點(diǎn)的effectList中。

  // 將此節(jié)點(diǎn)的effectList合并到到父節(jié)點(diǎn)的effectList中
  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;
  }
  // 若當(dāng)前 fiber 節(jié)點(diǎn)存在存在副作用(增,刪,改), 則將其加入到父節(jié)點(diǎn)的`effectList`中。
  const flags = completedWork.flags;
  if (flags > PerformedWork) {
    if (returnFiber.lastEffect !== null) {
      returnFiber.lastEffect.nextEffect = completedWork;
    } else {
      returnFiber.firstEffect = completedWork;
    }
    returnFiber.lastEffect = completedWork;
  }

effectList是一條用于收集存在effectTag的fiber節(jié)點(diǎn)的單向鏈表。React使用fiber.firstEffect表示掛載到此fiber節(jié)點(diǎn)的effectList的第一個(gè)fiber節(jié)點(diǎn),使用fiber.lastEffect表示掛載到此fiber節(jié)點(diǎn)的effectList的最后一個(gè)fiber節(jié)點(diǎn)。

effectList存在的目的是為了提升commit階段的工作效率。在commit階段,我們需要找出所有存在effectTag的fiber節(jié)點(diǎn)并依次執(zhí)行effectTag對(duì)應(yīng)操作。為了避免在commit階段再去做遍歷操作去尋找effectTag不為空的fiber節(jié)點(diǎn),React在completeUnitOfWork函數(shù)調(diào)用的過(guò)程中提前把所有存在effectTag的節(jié)點(diǎn)收集到effectList中,在commit階段,只需要遍歷effectList,并執(zhí)行各個(gè)節(jié)點(diǎn)的effectTag的對(duì)應(yīng)操作就好。

render階段結(jié)束

completeUnitOfWork的回溯過(guò)程中,如果completedWork === null,說(shuō)明workInProgress fiber tree中的所有節(jié)點(diǎn)都已完成了completeWorkworkInProgress fiber tree已經(jīng)構(gòu)建完成,至此,render階段全部工作完成。

后續(xù)我們將回到協(xié)調(diào)階段的入口函數(shù)performSyncWorkOnRoot(legacy模式)或performConcurrentWorkOnRoot(concurrent 模式)中,調(diào)用commitRoot(root)(其中root為fiberRootNode)來(lái)開啟commit階段的工作流程。

探索React源碼系列文章

探索React源碼:初探React fiber

探索React源碼:React Diff

探索React源碼:Reconciler

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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