Fiber 樹的構(gòu)建

我們先來看一個(gè)簡單的 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'));

首次渲染的調(diào)用棧如下圖

file

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

  1. Init
  2. Render
  3. Commit

Init Phase

render

很簡單,直接調(diào)用 legacyRenderSubtreeIntoContainer。

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

這里需要注意一點(diǎn),此時(shí)的 element 已經(jīng)不是 render 中傳入的 <App /> 了,而是經(jīng)過 React.createElement 轉(zhuǎn)換后的一個(gè) ReactElement 對象。

legacyRenderSubtreeIntoContainer

在這里我們可以看到方法取名的重要性,一個(gè)好的方法名可以讓你一眼就看出這個(gè)方法的作用。legacyRenderSubtreeIntoContainer,顧名思義,這是一個(gè)遺留的方法,作用是渲染子樹并將其掛載到 container 上。再來看一下入?yún)?,children 和 container 分別是之前傳入 render 方法的 App 元素和 id 為 root 的 DOM 元素,所以可以看出這個(gè)方法會根據(jù) App 元素生成對應(yīng)的 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);
}

下面來細(xì)看一下這個(gè)方法:

  1. 首次掛載時(shí),會通過 legacyCreateRootFromDOMContainer 方法創(chuàng)建 container.reactRootContainer 對象并賦值給 root。 container 對象現(xiàn)在長這樣:
file
  1. 初始化 fiberRoot 為 root.internalRoot,類型為 FiberRootNode。fiberRoot 有一個(gè)極其重要的 current 屬性,類型為 FiberNode,而 FiberNode 為 Fiber 節(jié)點(diǎn)的對應(yīng)的類型。所以說 current 對象是一個(gè) Fiber 節(jié)點(diǎn),不僅如此,它還是我們要構(gòu)造的 Fiber 樹的頭節(jié)點(diǎn),我們稱它為 rootFiber。到目前為止,我們可以得到下圖的指向關(guān)系:
file
  1. 將 fiberRoot 以及其它參數(shù)傳入 updateContainer 形成回調(diào)函數(shù),將回調(diào)函數(shù)傳入 unbatchedUpdates 并調(diào)用。

unbatchedUpdates

主要邏輯就是調(diào)用回調(diào)函數(shù) 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 方法做的還是一些雜活,我們簡單總結(jié)一下:

  1. 計(jì)算當(dāng)前 Fiber 節(jié)點(diǎn)的 lane(優(yōu)先級)。
  2. 根據(jù) lane(優(yōu)先級),創(chuàng)建當(dāng)前 Fiber 節(jié)點(diǎn)的 update 對象,并將其入隊(duì)。
  3. 調(diào)度當(dāng)前 Fiber 節(jié)點(diǎn)(rootFiber)。
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  const current = container.current;
  const eventTime = requestEventTime();
  // 計(jì)算當(dāng)前節(jié)點(diǎn)的 lane(優(yōu)先級)
  const lane = requestUpdateLane(current);
  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }
  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }
  // 根據(jù) lane(優(yōu)先級)計(jì)算當(dāng)前節(jié)點(diǎn)的 update 對象
  const update = createUpdate(eventTime, lane);
  update.payload = {element};
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }
  // 將 update 對象入隊(duì)
  enqueueUpdate(current, update);
  // 調(diào)度當(dāng)前 Fiber節(jié)點(diǎn)(rootFiber)
  scheduleUpdateOnFiber(current, lane, eventTime);
  return lane;
}

scheduleUpdateOnFiber

接著會進(jìn)入 scheduleUpdateOnFiber 方法,根據(jù) lane(優(yōu)先級)等于 SyncLane,代碼最終會執(zhí)行 performSyncWorkOnRoot 方法。performSyncWorkOnRoot 翻譯過來,就是指執(zhí)行根節(jié)點(diǎn)(rootFiber)的同步任務(wù),所以 ReactDOM.render 的首次渲染其實(shí)是一個(gè)同步的過程。

file

到這里大家可能會有個(gè)疑問,為什么 ReactDOM.render 觸發(fā)的首次渲染是一個(gè)同步的過程呢?不是說在新的 Fiber 架構(gòu)下,render 階段是一個(gè)可打斷的異步過程。
我們先來看看 lane 是怎么計(jì)算得到的,相關(guān)邏輯在 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 的計(jì)算是由當(dāng)前 Fiber 節(jié)點(diǎn)(rootFiber)的 mode 屬性決定的,這里的 mode 屬性其實(shí)指的就是當(dāng)前 Fiber 節(jié)點(diǎn)的渲染模式,而 rootFiber 的 mode 屬性其實(shí)最終是由 React 的啟動方式?jīng)Q定的。
React 其實(shí)有三種啟動模式:

  • Legacy Mode: ReactDOM.render(<App />, rootNode)。這是目前 React App 使用的方式,當(dāng)前沒有刪除這個(gè)模式的計(jì)劃,但是這個(gè)模式不支持一些新的功能。
  • Blocking Mode:ReactDOM.createBlockingRoot(rootNode).render(<App />)。目前正在實(shí)驗(yàn)中,作為遷移到 concurrent 模式的第一個(gè)步驟。
  • Concurrent Mode: ReactDOM.createRoot(rootNode).render(<App />)。目前正在實(shí)驗(yàn)中,在未來穩(wěn)定之后,將作為 React 的默認(rèn)啟動方式。此模式啟用所有新功能。

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

Render Phase

performSyncWorkOnRoot

核心是調(diào)用 renderRootSync 方法

renderRootSync

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

prepareFreshStack

首先調(diào)用 prepareFreshStack 方法,prepareFreshStack 中有一個(gè)重要的方法 createWorkInProgress。

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    // 通過 current 創(chuàng)建 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 其實(shí)就是在創(chuàng)建一個(gè) Fiber 節(jié)點(diǎn)。所以說 workInProgress 其實(shí)就是一個(gè) Fiber 節(jié)點(diǎn)。
從 createWorkInProgress 中,我們還可以看出:

  1. workInProgress 節(jié)點(diǎn)是 current 節(jié)點(diǎn)(rootFiber)的一個(gè)副本。
  2. workInProgress 節(jié)點(diǎn)與 current 節(jié)點(diǎn)(rootFiber)通過 alternate 屬性相互指向。

所以到現(xiàn)在為止,我們的 Fiber 樹如下:

file

workLoopSync

接下來調(diào)用 workLoopSync 方法,代碼很簡單,若 workInProgress 不為空,調(diào)用 performUnitOfWork 處理 workInProgress 節(jié)點(diǎn)。

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

performUnitOfWork

performUnitOfWork 有兩個(gè)重要的方法 beginWork 和 completeUnitOfWork,在 Fiber 的構(gòu)建過程中,我們只需重點(diǎn)關(guān)注 beginWork 這個(gè)方法。

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;
}

目前我們只能看出,它會對當(dāng)前的 workInProgress 節(jié)點(diǎn)進(jìn)行處理,至于怎么處理的,當(dāng)我們解析完 beginWork 方法再來總結(jié) performUnitOfWork 的作用。

beginWork

根據(jù) workInProgress 節(jié)點(diǎn)的 tag 進(jìn)行邏輯分發(fā)。tag 屬性代表的是當(dāng)前 Fiber 節(jié)點(diǎn)的類型,常見的有下面幾種:

  • FunctionComponent:函數(shù)組件(包括 Hooks)
  • ClassComponent:類組件
  • HostRoot:Fiber 樹根節(jié)點(diǎn)
  • HostComponent:DOM 元素
  • HostText:文本節(jié)點(diǎn)
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // 省略非核心(針對樹構(gòu)建)邏輯
  switch (workInProgress.tag) {
    // 省略部分 case 邏輯
    // 函數(shù)組件(包括 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,
      );
    }
    // 根節(jié)點(diǎn)
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    // DOM 元素
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    // 文本節(jié)點(diǎn)
    case HostText:
      return updateHostText(current, workInProgress);
    // 省略部分 case 邏輯
  }
  // 省略匹配不上的錯誤處理
}

當(dāng)前的 workInProgress 節(jié)點(diǎn)為 rootFiber,tag 對應(yīng)為 HostRoot,會調(diào)用 updateHostRoot 方法。

file

rootFiber 的 tag(HostRoot)是什么來的?核心代碼如下:

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

在創(chuàng)建 rootFiber 節(jié)點(diǎn)的時(shí)候,直接指定了 tag 參數(shù)為 HostRoot。

updateHostRoot

updateHostRoot 的主要邏輯如下:

  1. 調(diào)用 reconcileChildren 方法創(chuàng)建 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;
}

這里有一點(diǎn)需要注意,通過查看源碼,你會發(fā)現(xiàn)不僅是 updateHostRoot 方法,所以的更新方法最終都會調(diào)用下面這個(gè)方法:

reconcileChildren(current, workInProgress, nextChildren, renderLanes);

只是針對不同的節(jié)點(diǎn)類型,會有一些不同的處理,最終殊途同歸。

reconcileChildren

reconcileChildren 根據(jù) current 是否為空進(jìn)行邏輯分發(fā)。

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,
    );
  }
}

此時(shí) current 節(jié)點(diǎn)不為空,會走 else 邏輯,調(diào)用 reconcileChildFibers 創(chuàng)建 workInProgress.child 對象。

reconcileChildFibers

根據(jù) newChild 的類型進(jìn)行不同的邏輯處理。

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 很關(guān)鍵,我們先明確一下 newChild 究竟是什么?通過層層向上尋找,你會在 updateHostRoot 方法中發(fā)現(xiàn)它其實(shí)是最開始傳入 render 方法的 App 元素,它在 updateHostRoot 中被叫做 nextChildren,到這里我們可以做出這樣的猜想,rootFiber 的下一個(gè)是 App 節(jié)點(diǎn),并且 App 節(jié)點(diǎn)是由 App 元素生成的,下面來看一下 newChild 的結(jié)構(gòu):

file

可以看出 newChild 類型為 object,$$typeof 屬性為 REACT_ELEMENT_TYPE,所以會調(diào)用:

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

下面繼續(xù)看 reconcileSingleElement 這個(gè)方法:

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;
  }
}

方法的調(diào)用比較深,我們先明確一下入?yún)?,returnFiber 為 workInProgress 節(jié)點(diǎn),element 其實(shí)就是傳入的 newChild,也就是 App 元素,所以這個(gè)方法的作用為:

  1. 調(diào)用 createFiberFromElement 方法根據(jù) App 元素創(chuàng)建 App 節(jié)點(diǎn)。
  2. 將新生成的 App 節(jié)點(diǎn)的 return 屬性指向當(dāng)前 workInProgress 節(jié)點(diǎn)(rootFiber)。此時(shí) Fiber 樹如下圖:
file
  1. 返回 App 節(jié)點(diǎn)。
placeSingleChild

接下來調(diào)用 placeSingleChild:

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

入?yún)橹皠?chuàng)建的 App 節(jié)點(diǎn),它的作用為:

  1. 當(dāng)前的 App 節(jié)點(diǎn)打上一個(gè) Placement 的 flags,表示新增這個(gè)節(jié)點(diǎn)。
  2. 返回 App 節(jié)點(diǎn)。

之后 App 節(jié)點(diǎn)會被一路返回到的 reconcileChildren 方法:

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

此時(shí) workInProgress 節(jié)點(diǎn)的 child 屬性會指向 App 節(jié)點(diǎn)。此時(shí) Fiber 樹為:

file
beginWork 小結(jié)

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

  1. 根據(jù) workInProgress.tag 進(jìn)行邏輯分發(fā),調(diào)用形如 updateHostRoot、updateClassComponent 等更新方法。
  2. 所有的更新方法最終都會調(diào)用 reconcileChildren,reconcileChildren 根據(jù) current 進(jìn)行簡單的邏輯分發(fā)。
  3. 之后會調(diào)用 mountChildFibers/reconcileChildFibers 方法,它們的作用是根據(jù) ReactElement 對象生成 Fiber 節(jié)點(diǎn),并打上相應(yīng)的 flags,表示這個(gè)節(jié)點(diǎn)是新增,刪除還是更新等等。
  4. 最終返回新創(chuàng)建的 Fiber 節(jié)點(diǎn)。

簡單來說就是創(chuàng)建新的 Fiber 字節(jié)點(diǎn),并將其掛載到 Fiber 樹上,最后返回新創(chuàng)建的子節(jié)點(diǎn)。

performUnitOfWork 小結(jié)

下面我們來小結(jié)一下 performUnitOfWork 這個(gè)方法,先來回顧一下 workLoopSync 方法。

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

它會循環(huán)執(zhí)行 performUnitOfWork,而 performUnitOfWork,我們已經(jīng)知道它會通過 beginWork 創(chuàng)建新的 Fiber 節(jié)點(diǎn)。它還有另外一個(gè)作用,那就是把 workInProgress 更新為新創(chuàng)建的 Fiber 節(jié)點(diǎn),相關(guān)邏輯如下:

// 省略非核心代碼
// beginWork 返回新創(chuàng)建的 Fiber 節(jié)點(diǎn)并賦值給 next
next = beginWork(current, unitOfWork, subtreeRenderLanes);
// 省略非核心代碼
if (next === null) {
  completeUnitOfWork(unitOfWork);
} else {
  // 若 Fiber 節(jié)點(diǎn)不為空則將 workInProgress 更新為新創(chuàng)建的 Fiber 節(jié)點(diǎn)
  workInProgress = next;
}

所以當(dāng) performUnitOfWork 執(zhí)行完,當(dāng)前的 workInProgress 都存儲著下次要處理的 Fiber 節(jié)點(diǎn),為下一次的 workLoopSync 做準(zhǔn)備。
performUnitOfWork 作用總結(jié)如下:

  1. 通過調(diào)用 beginWork 創(chuàng)建新的 Fiber 節(jié)點(diǎn),并將其掛載到 Fiber 樹上
  2. 將 workInProgress 更新為新創(chuàng)建的 Fiber 節(jié)點(diǎn)。

App 節(jié)點(diǎn)的處理

rootFiber 節(jié)點(diǎn)處理完成之后,對應(yīng)的 Fiber 樹如下:

file

接下來 performUnitOfWork 會開始處理 App 節(jié)點(diǎn)。App 節(jié)點(diǎn)的處理過程大致與 rootFiber 節(jié)點(diǎn)類似,就是調(diào)用 beginWork 創(chuàng)建新的子節(jié)點(diǎn),也就是 className 為 container 的 div 節(jié)點(diǎn),處理完成之后的 Fiber 樹如下:

file

這里有一個(gè)很關(guān)鍵的地方需要大家注意。我們先回憶一下對 rootFiber 的處理,針對 rootFiber,我們已經(jīng)知道在 updateHostRoot 中,它會提取出 nextChildren,也就是最初傳入 render 方法的 element。
那針對 App 節(jié)點(diǎn),它是如何獲取 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 是一個(gè) class,React 首先會實(shí)例化會它:

file

之后會把生成的實(shí)例掛在到當(dāng)前 workInProgress 節(jié)點(diǎn),也就是 App 節(jié)點(diǎn)的 stateNode 屬性上:

file

然后在 updateClassComponent 方法中,會先初始化 instance 為 workInProgress.stateNode,之后調(diào)用 instance 的 render 方法并賦值給 nextChildren:

file

此時(shí)的 nextChildren 為下面 JSX 經(jīng)過 React.createElement 轉(zhuǎn)化后的結(jié)果:

<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 存儲的是其子節(jié)點(diǎn),它可以是對象也可以是數(shù)組。對于 App 節(jié)點(diǎn)和第一個(gè) div 節(jié)點(diǎn),它們都只有一個(gè)子節(jié)點(diǎn)。對于第二個(gè) div 節(jié)點(diǎn),它有三個(gè)子節(jié)點(diǎn),分別是 h1、p、p,所以它的 children 為數(shù)組。

并且 props 還會保存在新生成的 Fiber 節(jié)點(diǎn)的 pendingProps 屬性上,相關(guān)邏輯如下:

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;
}

第一個(gè) div 節(jié)點(diǎn)的處理

App 節(jié)點(diǎn)的 nextChildren 是通過構(gòu)造實(shí)例并調(diào)用 App 組件內(nèi)的 render 方法得到的,那對于第一個(gè) div 節(jié)點(diǎn),它的 nextChildren 是如何獲取的呢?
針對 div 節(jié)點(diǎn),它的 tag 為 HostComponent,所以在 beginWork 中會調(diào)用 updateHostComponent 方法,可以看出 nextChildren 是從當(dāng)前 workInProgress 節(jié)點(diǎn)的 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;
}

我們之前說過,在創(chuàng)建新的 Fiber 節(jié)點(diǎn)時(shí),我們會把下一個(gè)子節(jié)點(diǎn)元素保存在 pendingProps 中。當(dāng)下次調(diào)用更新方法(形如 updateHostComponent )時(shí),我們就可以直接從 pendingProps 中獲取下一個(gè)子元素。
之后的邏輯同上,處理完第一個(gè) div 節(jié)點(diǎn)后的 Fiber 樹如下圖:

file

第二個(gè) div 節(jié)點(diǎn)的處理

我們先看一下第二個(gè) div 節(jié)點(diǎn):

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

它比較特殊,有三個(gè)字節(jié)點(diǎn),對應(yīng)的 nextChildren 為

file

下面我們來看看 React 是如何處理多節(jié)點(diǎn)的情況,首先我們還是會進(jìn)入 reconcileChildFibers 這個(gè)方法:

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

newChild 即是 nextChildren,為數(shù)組,會調(diào)用 reconcileChildrenArray 這個(gè)方法

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;
  }
  // 省略非核心邏輯
}

下面來總結(jié)一下這個(gè)方法:

  1. 遍歷所有的子元素,通過 createChild 方法根據(jù)子元素創(chuàng)建子節(jié)點(diǎn),并將每個(gè)字元素的 return 屬性指向父節(jié)點(diǎn)。
  2. 用 resultingFirstChild 來標(biāo)識第一個(gè)子元素。
  3. 將子元素用 sibling 相連。

最后我們的 Fiber 樹就構(gòu)建完成了,如下圖:

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

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

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