我們先來看一個(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)用棧如下圖

以 performSyncWorkOnRoot 和 commitRoot 兩個(gè)方法為界限,可以把 ReactDOM.render 分為三個(gè)階段:
- Init
- Render
- 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è)方法:
- 首次掛載時(shí),會通過 legacyCreateRootFromDOMContainer 方法創(chuàng)建 container.reactRootContainer 對象并賦值給 root。 container 對象現(xiàn)在長這樣:

- 初始化 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)系:

- 將 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é)一下:
- 計(jì)算當(dāng)前 Fiber 節(jié)點(diǎn)的 lane(優(yōu)先級)。
- 根據(jù) lane(優(yōu)先級),創(chuàng)建當(dāng)前 Fiber 節(jié)點(diǎn)的 update 對象,并將其入隊(duì)。
- 調(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è)同步的過程。

到這里大家可能會有個(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 中,我們還可以看出:
- workInProgress 節(jié)點(diǎn)是 current 節(jié)點(diǎn)(rootFiber)的一個(gè)副本。
- workInProgress 節(jié)點(diǎn)與 current 節(jié)點(diǎn)(rootFiber)通過 alternate 屬性相互指向。
所以到現(xiàn)在為止,我們的 Fiber 樹如下:

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 方法。

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 的主要邏輯如下:
- 調(diào)用 reconcileChildren 方法創(chuàng)建 workInProgress.child。
- 返回 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):

可以看出 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è)方法的作用為:
- 調(diào)用 createFiberFromElement 方法根據(jù) App 元素創(chuàng)建 App 節(jié)點(diǎn)。
- 將新生成的 App 節(jié)點(diǎn)的 return 屬性指向當(dāng)前 workInProgress 節(jié)點(diǎn)(rootFiber)。此時(shí) Fiber 樹如下圖:

- 返回 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),它的作用為:
- 當(dāng)前的 App 節(jié)點(diǎn)打上一個(gè) Placement 的 flags,表示新增這個(gè)節(jié)點(diǎn)。
- 返回 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 樹為:

beginWork 小結(jié)
beginWork 的鏈路比較長,我們來梳理一下:
- 根據(jù) workInProgress.tag 進(jìn)行邏輯分發(fā),調(diào)用形如 updateHostRoot、updateClassComponent 等更新方法。
- 所有的更新方法最終都會調(diào)用 reconcileChildren,reconcileChildren 根據(jù) current 進(jìn)行簡單的邏輯分發(fā)。
- 之后會調(diào)用 mountChildFibers/reconcileChildFibers 方法,它們的作用是根據(jù) ReactElement 對象生成 Fiber 節(jié)點(diǎn),并打上相應(yīng)的 flags,表示這個(gè)節(jié)點(diǎn)是新增,刪除還是更新等等。
- 最終返回新創(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é)如下:
- 通過調(diào)用 beginWork 創(chuàng)建新的 Fiber 節(jié)點(diǎn),并將其掛載到 Fiber 樹上
- 將 workInProgress 更新為新創(chuàng)建的 Fiber 節(jié)點(diǎn)。
App 節(jié)點(diǎn)的處理
rootFiber 節(jié)點(diǎn)處理完成之后,對應(yīng)的 Fiber 樹如下:

接下來 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 樹如下:

這里有一個(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í)例化會它:

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

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

此時(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 長啥樣:

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 樹如下圖:

第二個(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 為

下面我們來看看 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è)方法:
- 遍歷所有的子元素,通過 createChild 方法根據(jù)子元素創(chuàng)建子節(jié)點(diǎn),并將每個(gè)字元素的 return 屬性指向父節(jié)點(diǎn)。
- 用 resultingFirstChild 來標(biāo)識第一個(gè)子元素。
- 將子元素用 sibling 相連。
最后我們的 Fiber 樹就構(gòu)建完成了,如下圖:
