Fiber 樹的構建

袋鼠雲數棧前端發表於2021-06-30

我們先來看一個簡單的 demo:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
class App extends React.Component {
    render() {
        return (
            <div className="container">
                <div className="section">
                    <h1>This is the title.</h1>
                    <p>This is the first paragraph.</p>
                    <p>This is the second paragraph.</p>
                </div>
            </div>
        );
    }
}
ReactDOM.render(<App />, document.getElementById('root'));

首次渲染的呼叫棧如下圖

file

以 performSyncWorkOnRoot 和 commitRoot 兩個方法為界限,可以把 ReactDOM.render 分為三個階段:

  1. Init
  2. Render
  3. Commit

Init Phase

render

很簡單,直接呼叫 legacyRenderSubtreeIntoContainer。

export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {
  // 省略對 container 的校驗邏輯
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

這裡需要注意一點,此時的 element 已經不是 render 中傳入的 了,而是經過 React.createElement 轉換後的一個 ReactElement 物件。

legacyRenderSubtreeIntoContainer

在這裡我們可以看到方法取名的重要性,一個好的方法名可以讓你一眼就看出這個方法的作用。legacyRenderSubtreeIntoContainer,顧名思義,這是一個遺留的方法,作用是渲染子樹並將其掛載到 container 上。再來看一下入參,children 和 container 分別是之前傳入 render 方法的 App 元素和 id 為 root 的 DOM 元素,所以可以看出這個方法會根據 App 元素生成對應的 DOM 樹,並將其掛在到 root 元素上。

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  if (!root) {
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    fiberRoot = root._internalRoot;
	// 省略對 callback 的處理邏輯
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    // 省略 else 邏輯
  }
  return getPublicRootInstance(fiberRoot);
}

下面來細看一下這個方法:

  1. 首次掛載時,會通過 legacyCreateRootFromDOMContainer 方法建立 container.reactRootContainer 物件並賦值給 root。 container 物件現在長這樣:

file

  1. 初始化 fiberRoot 為 root.internalRoot,型別為 FiberRootNode。fiberRoot 有一個極其重要的 current 屬性,型別為 FiberNode,而 FiberNode 為 Fiber 節點的對應的型別。所以說 current 物件是一個 Fiber 節點,不僅如此,它還是我們要構造的 Fiber 樹的頭節點,我們稱它為 rootFiber。到目前為止,我們可以得到下圖的指向關係:

file

  1. 將 fiberRoot 以及其它引數傳入 updateContainer 形成回撥函式,將回撥函式傳入 unbatchedUpdates 並呼叫。

unbatchedUpdates

主要邏輯就是呼叫回撥函式 fn,也就是之前傳入的 updateContainer。

export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
  const prevExecutionContext = executionContext;
  executionContext &= ~BatchedContext;
  executionContext |= LegacyUnbatchedContext;
  try {
	// fn 為之前傳入的 updateContainer
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;
    if (executionContext === NoContext) {
      resetRenderTimer();
      flushSyncCallbackQueue();
    }
  }
}

updateContainer

updateContainer 方法做的還是一些雜活,我們簡單總結一下:

  1. 計算當前 Fiber 節點的 lane(優先順序)。
  2. 根據 lane(優先順序),建立當前 Fiber 節點的 update 物件,並將其入隊。
  3. 排程當前 Fiber 節點(rootFiber)。
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  const current = container.current;
  const eventTime = requestEventTime();
  // 計算當前節點的 lane(優先順序)
  const lane = requestUpdateLane(current);
  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }
  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }
  // 根據 lane(優先順序)計算當前節點的 update 物件
  const update = createUpdate(eventTime, lane);
  update.payload = {element};
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }
  // 將 update 物件入隊
  enqueueUpdate(current, update);
  // 排程當前 Fiber節點(rootFiber)
  scheduleUpdateOnFiber(current, lane, eventTime);
  return lane;
}

scheduleUpdateOnFiber

接著會進入 scheduleUpdateOnFiber 方法,根據 lane(優先順序)等於 SyncLane,程式碼最終會執行 performSyncWorkOnRoot 方法。performSyncWorkOnRoot 翻譯過來,就是指執行根節點(rootFiber)的同步任務,所以 ReactDOM.render 的首次渲染其實是一個同步的過程。

file

到這裡大家可能會有個疑問,為什麼 ReactDOM.render 觸發的首次渲染是一個同步的過程呢?不是說在新的 Fiber 架構下,render 階段是一個可打斷的非同步過程。
我們先來看看 lane 是怎麼計算得到的,相關邏輯在 updateContainer 中的 requestUpdateLane 方法裡:

export function requestUpdateLane(fiber: Fiber): Lane {
  const mode = fiber.mode;
  if ((mode & BlockingMode) === NoMode) {
    return (SyncLane: Lane);
  } else if ((mode & ConcurrentMode) === NoMode) {
    return getCurrentPriorityLevel() === ImmediateSchedulerPriority
      ? (SyncLane: Lane)
      : (SyncBatchedLane: Lane);
  } else if (
    !deferRenderPhaseUpdateToNextBatch &&
    (executionContext & RenderContext) !== NoContext &&
    workInProgressRootRenderLanes !== NoLanes
  ) {
   return pickArbitraryLane(workInProgressRootRenderLanes);
  }
  // 省略非核心程式碼
}

可以看出 lane 的計算是由當前 Fiber 節點(rootFiber)的 mode 屬性決定的,這裡的 mode 屬性其實指的就是當前 Fiber 節點的渲染模式,而 rootFiber 的 mode 屬性其實最終是由 React 的啟動方式決定的。
React 其實有三種啟動模式:

  • Legacy Mode: ReactDOM.render(<App />, rootNode)。這是目前 React App 使用的方式,當前沒有刪除這個模式的計劃,但是這個模式不支援一些新的功能。
  • Blocking Mode:ReactDOM.createBlockingRoot(rootNode).render(<App />)。目前正在實驗中,作為遷移到 concurrent 模式的第一個步驟。
  • Concurrent Mode: ReactDOM.createRoot(rootNode).render(<App />)。目前正在實驗中,在未來穩定之後,將作為 React 的預設啟動方式。此模式啟用所有新功能。

因此不同的渲染模式在掛載階段的差異,本質上來說並不是工作流的差異(其工作流涉及 初始化 → render → commit 這 3 個步驟),而是 mode 屬性的差異。mode 屬性決定著這個工作流是一氣呵成(同步)的,還是分片執行(非同步)的。

Render Phase

performSyncWorkOnRoot

核心是呼叫 renderRootSync 方法

renderRootSync

有兩個核心方法 prepareFreshStack 和 workLoopSync,下面來逐個分析。

prepareFreshStack

首先呼叫 prepareFreshStack 方法,prepareFreshStack 中有一個重要的方法 createWorkInProgress。

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
	// 通過 current 建立 workInProgress
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;
	// 使 workInProgress 與 current 通過 alternate 相互指向
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
	// 省略 else 邏輯
  }
  // 省略對 workInProgress 屬性的處理邏輯
  return workInProgress;
}

下面我們來看一下 workInProgress 究竟是什麼?workInProgress 是 createFiber 的返回值,接著來看一下 createFiber。

const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {
  return new FiberNode(tag, pendingProps, key, mode);
};

可以看出 createFiber 其實就是在建立一個 Fiber 節點。所以說 workInProgress 其實就是一個 Fiber 節點。
從 createWorkInProgress 中,我們還可以看出:

  1. workInProgress 節點是 current 節點(rootFiber)的一個副本。
  2. workInProgress 節點與 current 節點(rootFiber)通過 alternate 屬性相互指向。

所以到現在為止,我們的 Fiber 樹如下:

file

workLoopSync

接下來呼叫 workLoopSync 方法,程式碼很簡單,若 workInProgress 不為空,呼叫 performUnitOfWork 處理 workInProgress 節點。

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

performUnitOfWork

performUnitOfWork 有兩個重要的方法 beginWork 和 completeUnitOfWork,在 Fiber 的構建過程中,我們只需重點關注 beginWork 這個方法。

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);
  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }
  resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
  ReactCurrentOwner.current = null;
}

目前我們只能看出,它會對當前的 workInProgress 節點進行處理,至於怎麼處理的,當我們解析完 beginWork 方法再來總結 performUnitOfWork 的作用。

beginWork

根據 workInProgress 節點的 tag 進行邏輯分發。tag 屬性代表的是當前 Fiber 節點的型別,常見的有下面幾種:

  • FunctionComponent:函式元件(包括 Hooks)
  • ClassComponent:類元件
  • HostRoot:Fiber 樹根節點
  • HostComponent:DOM 元素
  • HostText:文字節點
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // 省略非核心(針對樹構建)邏輯
  switch (workInProgress.tag) {
	// 省略部分 case 邏輯
	// 函式元件(包括 Hooks)
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
	// 類元件
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
	// 根節點
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
	// DOM 元素
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
	// 文字節點
    case HostText:
      return updateHostText(current, workInProgress);
	// 省略部分 case 邏輯
  }
  // 省略匹配不上的錯誤處理
}

當前的 workInProgress 節點為 rootFiber,tag 對應為 HostRoot,會呼叫 updateHostRoot 方法。

file

rootFiber 的 tag(HostRoot)是什麼來的?核心程式碼如下:

export function createHostRootFiber(tag: RootTag): Fiber {
  // 省略非核心程式碼
  return createFiber(HostRoot, null, null, mode);
}

在建立 rootFiber 節點的時候,直接指定了 tag 引數為 HostRoot。

updateHostRoot

updateHostRoot 的主要邏輯如下:

  1. 呼叫 reconcileChildren 方法建立 workInProgress.child。
  2. 返回 workInProgress.child。
function updateHostRoot(current, workInProgress, renderLanes) {
	// 省略非核心邏輯
  if (root.hydrate && enterHydrationState(workInProgress)) {
  	// 省略 if 成立的邏輯
  } else {
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
    resetHydrationState();
  }
  return workInProgress.child;
}

這裡有一點需要注意,通過檢視原始碼,你會發現不僅是 updateHostRoot 方法,所以的更新方法最終都會呼叫下面這個方法:

reconcileChildren(current, workInProgress, nextChildren, renderLanes);

只是針對不同的節點型別,會有一些不同的處理,最終殊途同歸。

reconcileChildren

reconcileChildren 根據 current 是否為空進行邏輯分發。

export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  if (current === null) {
   workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}

此時 current 節點不為空,會走 else 邏輯,呼叫 reconcileChildFibers 建立 workInProgress.child 物件。

reconcileChildFibers

根據 newChild 的型別進行不同的邏輯處理。

function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    lanes: Lanes,
  ): Fiber | null {
	// 省略非核心程式碼
    const isObject = typeof newChild === 'object' && newChild !== null;
    if (isObject) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              lanes,
            ),
          );
	  // 省略其他 case 邏輯
     }
    }
	// 省略非核心程式碼
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }
	// 省略非核心程式碼
  }

newChild 很關鍵,我們先明確一下 newChild 究竟是什麼?通過層層向上尋找,你會在 updateHostRoot 方法中發現它其實是最開始傳入 render 方法的 App 元素,它在 updateHostRoot 中被叫做 nextChildren,到這裡我們可以做出這樣的猜想,rootFiber 的下一個是 App 節點,並且 App 節點是由 App 元素生成的,下面來看一下 newChild 的結構:

file

可以看出 newChild 型別為 object,$$typeof 屬性為 REACT_ELEMENT_TYPE,所以會呼叫:

placeSingleChild(
  reconcileSingleElement(
    returnFiber,
    currentFirstChild,
    newChild,
    lanes,
  ),
);
reconcileSingleElement

下面繼續看 reconcileSingleElement 這個方法:

function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  lanes: Lanes,
): Fiber {
  const key = element.key;
  let child = currentFirstChild;
  
  // 省略 child 不存在的處理邏輯
  if (element.type === REACT_FRAGMENT_TYPE) {
	// 省略 if 成立的處理邏輯
  } else {
    const created = createFiberFromElement(element, returnFiber.mode, lanes);
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}

方法的呼叫比較深,我們先明確一下入參,returnFiber 為 workInProgress 節點,element 其實就是傳入的 newChild,也就是 App 元素,所以這個方法的作用為:

  1. 呼叫 createFiberFromElement 方法根據 App 元素建立 App 節點。
  2. 將新生成的 App 節點的 return 屬性指向當前 workInProgress 節點(rootFiber)。此時 Fiber 樹如下圖:

file

  1. 返回 App 節點。
placeSingleChild

接下來呼叫 placeSingleChild:

function placeSingleChild(newFiber: Fiber): Fiber {
  if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags = Placement;
  }
  return newFiber;
}

入參為之前建立的 App 節點,它的作用為:

  1. 當前的 App 節點打上一個 Placement 的 flags,表示新增這個節點。
  2. 返回 App 節點。

之後 App 節點會被一路返回到的 reconcileChildren 方法:

workInProgress.child = reconcileChildFibers(
  workInProgress,
  current.child,
  nextChildren,
  renderLanes,
);

此時 workInProgress 節點的 child 屬性會指向 App 節點。此時 Fiber 樹為:

file

beginWork 小結

beginWork 的鏈路比較長,我們來梳理一下:

  1. 根據 workInProgress.tag 進行邏輯分發,呼叫形如 updateHostRoot、updateClassComponent 等更新方法。
  2. 所有的更新方法最終都會呼叫 reconcileChildren,reconcileChildren 根據 current 進行簡單的邏輯分發。
  3. 之後會呼叫 mountChildFibers/reconcileChildFibers 方法,它們的作用是根據 ReactElement 物件生成 Fiber 節點,並打上相應的 flags,表示這個節點是新增,刪除還是更新等等。
  4. 最終返回新建立的 Fiber 節點。

簡單來說就是建立新的 Fiber 位元組點,並將其掛載到 Fiber 樹上,最後返回新建立的子節點。

performUnitOfWork 小結

下面我們來小結一下 performUnitOfWork 這個方法,先來回顧一下 workLoopSync 方法。

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

它會迴圈執行 performUnitOfWork,而 performUnitOfWork,我們已經知道它會通過 beginWork 建立新的 Fiber 節點。它還有另外一個作用,那就是把 workInProgress 更新為新建立的 Fiber 節點,相關邏輯如下:

// 省略非核心程式碼
// beginWork 返回新建立的 Fiber 節點並賦值給 next
next = beginWork(current, unitOfWork, subtreeRenderLanes);
// 省略非核心程式碼
if (next === null) {
  completeUnitOfWork(unitOfWork);
} else {
  // 若 Fiber 節點不為空則將 workInProgress 更新為新建立的 Fiber 節點
  workInProgress = next;
}

所以當 performUnitOfWork 執行完,當前的 workInProgress 都儲存著下次要處理的 Fiber 節點,為下一次的 workLoopSync 做準備。
performUnitOfWork 作用總結如下:

  1. 通過呼叫 beginWork 建立新的 Fiber 節點,並將其掛載到 Fiber 樹上
  2. 將 workInProgress 更新為新建立的 Fiber 節點。

App 節點的處理

rootFiber 節點處理完成之後,對應的 Fiber 樹如下:

file

接下來 performUnitOfWork 會開始處理 App 節點。App 節點的處理過程大致與 rootFiber 節點類似,就是呼叫 beginWork 建立新的子節點,也就是 className 為 container 的 div 節點,處理完成之後的 Fiber 樹如下:

file

這裡有一個很關鍵的地方需要大家注意。我們先回憶一下對 rootFiber 的處理,針對 rootFiber,我們已經知道在 updateHostRoot 中,它會提取出 nextChildren,也就是最初傳入 render 方法的 element。
那針對 App 節點,它是如何獲取 nextChildren 的呢?先來看下我們的 App 元件:

class App extends React.Component {
    render() {
        return (
            <div className="container">
                <div className="section">
                    <h1>This is the title.</h1>
                    <p>This is the first paragraph.</p>
                    <p>This is the second paragraph.</p>
                </div>
            </div>
        );
    }
}

我們的 App 是一個 class,React 首先會例項化會它:

file

之後會把生成的例項掛在到當前 workInProgress 節點,也就是 App 節點的 stateNode 屬性上:

file

然後在 updateClassComponent 方法中,會先初始化 instance 為 workInProgress.stateNode,之後呼叫 instance 的 render 方法並賦值給 nextChildren:

file

此時的 nextChildren 為下面 JSX 經過 React.createElement 轉化後的結果:

<div className="container">
    <div className="section">
        <h1>This is the title.</h1>
        <p>This is the first paragraph.</p>
        <p>This is the second paragraph.</p>
    </div>
</div>

接著來看一下 nextChildren 長啥樣:

file

props.children 儲存的是其子節點,它可以是物件也可以是陣列。對於 App 節點和第一個 div 節點,它們都只有一個子節點。對於第二個 div 節點,它有三個子節點,分別是 h1、p、p,所以它的 children 為陣列。

並且 props 還會儲存在新生成的 Fiber 節點的 pendingProps 屬性上,相關邏輯如下:

export function createFiberFromElement(
  element: ReactElement,
  mode: TypeOfMode,
  lanes: Lanes,
): Fiber {
  let owner = null;
  const type = element.type;
  const key = element.key;
  const pendingProps = element.props;
  const fiber = createFiberFromTypeAndProps(
    type,
    key,
    pendingProps,
    owner,
    mode,
    lanes,
  );
  return fiber;
}
export function createFiberFromTypeAndProps(
  type: any, // React$ElementType
  key: null | string,
  pendingProps: any,
  owner: null | Fiber,
  mode: TypeOfMode,
  lanes: Lanes,
): Fiber {
  // 省略非核心邏輯
  const fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type;
  fiber.type = resolvedType;
  fiber.lanes = lanes;
  return fiber;
}

第一個 div 節點的處理

App 節點的 nextChildren 是通過構造例項並呼叫 App 元件內的 render 方法得到的,那對於第一個 div 節點,它的 nextChildren 是如何獲取的呢?
針對 div 節點,它的 tag 為 HostComponent,所以在 beginWork 中會呼叫 updateHostComponent 方法,可以看出 nextChildren 是從當前 workInProgress 節點的 pendingProps 上獲取的。

function updateHostComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) {
  // 省略非核心邏輯
  const nextProps = workInProgress.pendingProps;
  // 省略非核心邏輯
  let nextChildren = nextProps.children;
  // 省略非核心邏輯
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

我們之前說過,在建立新的 Fiber 節點時,我們會把下一個子節點元素儲存在 pendingProps 中。當下次呼叫更新方法(形如 updateHostComponent )時,我們就可以直接從 pendingProps 中獲取下一個子元素。
之後的邏輯同上,處理完第一個 div 節點後的 Fiber 樹如下圖:

file

第二個 div 節點的處理

我們先看一下第二個 div 節點:

<div className="section">
  <h1>This is the title.</h1>
  <p>This is the first paragraph.</p>
  <p>This is the second paragraph.</p>
</div>

它比較特殊,有三個位元組點,對應的 nextChildren 為

file

下面我們來看看 React 是如何處理多節點的情況,首先我們還是會進入 reconcileChildFibers 這個方法:

function reconcileChildFibers(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChild: any,
  lanes: Lanes,
): Fiber | null {
  
  // 省略非核心程式碼
  if (isArray(newChild)) {
    return reconcileChildrenArray(
      returnFiber,
      currentFirstChild,
      newChild,
      lanes,
    );
  }
  
  // 省略非核心程式碼
}

newChild 即是 nextChildren,為陣列,會呼叫 reconcileChildrenArray 這個方法

function reconcileChildrenArray(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChildren: Array<*>,
  lanes: Lanes,
): Fiber | null {
  // 省略非核心邏輯
  let previousNewFiber: Fiber | null = null;
  let oldFiber = currentFirstChild;
  // 省略非核心邏輯
  if (oldFiber === null) {
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
      if (newFiber === null) {
        continue;
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
    }
    return resultingFirstChild;
  }
  // 省略非核心邏輯
}

下面來總結一下這個方法:

  1. 遍歷所有的子元素,通過 createChild 方法根據子元素建立子節點,並將每個字元素的 return 屬性指向父節點。
  2. 用 resultingFirstChild 來標識第一個子元素。
  3. 將子元素用 sibling 相連。

最後我們的 Fiber 樹就構建完成了,如下圖:

file

相關文章