React 源碼中最重要的部分,你知道有哪些嗎?
無(wú)論是并發(fā)模式,還是同步模式,最終要生成新的 Fiber Tree,都是通過(guò)遍歷 workInProgress 的方式去執(zhí)行 performUnitOfWork。
// 并發(fā)模式
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
// 同步
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
需要特別注意的是這里的 workInProgress 表示當(dāng)前正在執(zhí)行的 Fiber 節(jié)點(diǎn),他會(huì)在遞歸的過(guò)程中不斷改變指向,這里要結(jié)合我們之前章節(jié)中分享過(guò)的 Fiber Tree 的鏈表結(jié)構(gòu)來(lái)理解。
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
一、performUnitOfWork
該方法主要用于創(chuàng)建 Fiber Tree,是否理解 Fiber Tree 的構(gòu)建過(guò)程,跟我們是否能做好性能優(yōu)化有非常直接的關(guān)系,因此對(duì)我而言,這是 React 源碼中最重要的一個(gè)部分。
從他的第一行代碼我們就能知道,F(xiàn)iber Tree 的創(chuàng)建是依賴(lài)于雙緩存策略。上一輪構(gòu)建完成的 Fiber tree,在代碼中用 current 來(lái)表示。
正在構(gòu)建中的 Fiber tree,在代碼中用 workInProgress 來(lái)表示,并且他們之間同層節(jié)點(diǎn)都用 alternate 相互指向。
current.alternate = workInProgress;
workInProgress.alternate = current;
workInProgress 會(huì)基于 current 構(gòu)建。
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
...
整體的思路是從 current[rootFiber] 樹(shù)往下執(zhí)行深度遍歷,在遍歷的過(guò)程中,會(huì)根據(jù) key、props、context、state 等條件進(jìn)行判斷,判斷結(jié)果如果發(fā)現(xiàn)節(jié)點(diǎn)沒(méi)有發(fā)生變化,那么就復(fù)用 current 的節(jié)點(diǎn),如果發(fā)生了變化,則重新創(chuàng)建 Fiber 節(jié)點(diǎn),并標(biāo)記需要修改的類(lèi)型,用于傳遞給 commitRoot。
二、beginWork
每一個(gè)被遍歷到的 Fiber 節(jié)點(diǎn),會(huì)執(zhí)行 beginWork 方法。
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
該方法根據(jù)傳入的 Fiber 節(jié)點(diǎn)創(chuàng)建子節(jié)點(diǎn),并將這兩個(gè)節(jié)點(diǎn)連接起來(lái)。
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {...}
React 在 ReactFiberBeginWork.new.js 模塊中維護(hù)了一個(gè)全局的 didReceiveUpdate 變量,來(lái)表示當(dāng)前節(jié)點(diǎn)是否需要更新。
let didReceiveUpdate: boolean = false;
在 beginWork 的執(zhí)行過(guò)程中,會(huì)經(jīng)歷一些判斷來(lái)確認(rèn) didReceiveUpdate 的值,從而判斷該 Fiber 節(jié)點(diǎn)是否需要重新執(zhí)行。
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
didReceiveUpdate = true;
} else {
這里比較的是 props 和 context 是否發(fā)生了變化。當(dāng)他們其中一個(gè)變化時(shí),則將 didReceiveUpdate 設(shè)置為 true。
這里的 hasLegacyContextChanged() 兼容的是舊版本 的 context,新版本的 context 是否發(fā)生變化會(huì)反應(yīng)到 pending update 中,也就是使用下面的 checkScheduledUpdateOrContext 來(lái)查看是否有更新的調(diào)度任務(wù)。
當(dāng) props 和 context 都沒(méi)有發(fā)生變化,并且也不存在對(duì)應(yīng)的調(diào)度任務(wù)時(shí),將其設(shè)置為 false。
如果有 state/context 發(fā)生變化,則會(huì)存在調(diào)度任務(wù)。
} else {
// Neither props nor legacy context changes. Check if there's a pending
// update or context change.
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
// If this is the second pass of an error or suspense boundary, there
// may not be work scheduled on `current`, so we check for this flag.
(workInProgress.flags & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
這里有一個(gè)很關(guān)鍵的點(diǎn),就在于當(dāng)方法進(jìn)入到 attemptEarlyBailoutIfNoScheduledUpdate 去判斷子節(jié)點(diǎn)是否可以 bailout 時(shí),他并沒(méi)有比較子節(jié)點(diǎn)的 props。
核心的邏輯在 bailoutOnAlreadyFinishedWork 中。
{
...
// 判斷子節(jié)點(diǎn)是否有 pending 任務(wù)要做
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
// The children don't have any work either. We can skip them.
return null
}
// This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue.
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
所以這里有一個(gè)很重要的思考就是為什么判斷子節(jié)點(diǎn)是否發(fā)生變化時(shí),并沒(méi)有去比較 props,這是性能優(yōu)化策略的關(guān)鍵一步,結(jié)合我們之前講的性能優(yōu)化策略去理解,你就能知道答案。
回到 beginWork, 后續(xù)的邏輯會(huì)根據(jù)不同的 tag,創(chuàng)建不同類(lèi)型的 Fiber 節(jié)點(diǎn)。
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
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);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
// ...
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
const outerPropTypes = type.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentNameFromType(type),
);
}
}
}
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
}
// ... 其他類(lèi)型
我們重點(diǎn)關(guān)注 updateFunctionComponent 的執(zhí)行邏輯,可以發(fā)現(xiàn),當(dāng) didReceiveUpdate 為 false 時(shí),會(huì)執(zhí)行 bailout 跳過(guò)創(chuàng)建過(guò)程。
if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
如果無(wú)法 bailout,則最后執(zhí)行 reconcileChildren 創(chuàng)建新的子節(jié)點(diǎn)。
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
另外我們還應(yīng)該關(guān)注 updateMemoComponent 中的邏輯。該邏輯通過(guò)淺比較函數(shù) shallowEqual 來(lái)比較更新前后兩個(gè) props 的差異。當(dāng)比較結(jié)果為 true 時(shí),也是調(diào)用 bailout 跳過(guò)創(chuàng)建。
而不是沿用 didReceiveUpdate 的結(jié)果。
if (!hasScheduledUpdateOrContext) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
二、completeWork
在 performUnitOfWork 執(zhí)行過(guò)程中,當(dāng)發(fā)現(xiàn)當(dāng)前節(jié)點(diǎn)已經(jīng)沒(méi)有子節(jié)點(diǎn)了,就會(huì)調(diào)用 completeUnitOfWork 方法。
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
該方法主要用于執(zhí)行 completeWork。completeWork 主要的作用是用于創(chuàng)建與 Fiber 節(jié)點(diǎn)對(duì)應(yīng)的 DOM 節(jié)點(diǎn)。
這里創(chuàng)建的 DOM 節(jié)點(diǎn)并沒(méi)有插入到 HTML 中,還存在于內(nèi)存里。
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress, false, false);
由于 completeWork 的執(zhí)行是從葉子節(jié)點(diǎn),往根節(jié)點(diǎn)執(zhí)行,因此,每次我們將新創(chuàng)建的節(jié)點(diǎn) append 到父節(jié)點(diǎn),執(zhí)行到最后 rootFiber 時(shí),一個(gè)完整的 DOM 樹(shù)就已經(jīng)構(gòu)建完成了。
completeWork 的執(zhí)行順序是一個(gè)回溯的過(guò)程。
當(dāng)然,F(xiàn)iber 節(jié)點(diǎn)與 DOM 節(jié)點(diǎn)之間,也會(huì)保持一一對(duì)應(yīng)的引用關(guān)系,因此在更新階段,我們能夠輕易的判斷和復(fù)用已經(jīng)存在的 DOM 節(jié)點(diǎn)從而避免重復(fù)創(chuàng)建。
四、遍歷順序
beginWork 和 completeWork 的執(zhí)行順序理解起來(lái)比較困難,為了便于理解,我們這里用一個(gè)圖示來(lái)表達(dá)。
例如有這樣一個(gè)結(jié)構(gòu)的節(jié)點(diǎn)。
<div id="root">
<div className="1">
<div className="1-1">1-1</div>
<div className="1-2">1-2</div>
<div className="1-3">
<div className="1-3-1">1-3-1</div>
</div>
</div>
<div className="2">2</div>
<div className="3">3</div>
</div>
beginWork 的執(zhí)行是按照 Fiber 節(jié)點(diǎn)的鏈表深度遍歷執(zhí)行。
completeWork 則是當(dāng) fiber.next === null 時(shí)開(kāi)始執(zhí)行,他一個(gè)從葉子節(jié)點(diǎn)往根節(jié)點(diǎn)執(zhí)行的回溯過(guò)程。當(dāng)葉子節(jié)點(diǎn)被執(zhí)行過(guò)后,則對(duì)葉子節(jié)點(diǎn)的父節(jié)點(diǎn)執(zhí)行 completeWork。
下圖就是上面 demo 的執(zhí)行順序。
其中藍(lán)色圓代表對(duì)應(yīng)節(jié)點(diǎn)的 beginWork 執(zhí)行。黃色圓代表對(duì)應(yīng)節(jié)點(diǎn)的 completeWork 執(zhí)行。
五、總結(jié)
beginWork 與 completeWork 的執(zhí)行是 React 源碼中最重要的部分,理解他們的核心邏輯能有效幫助我們做好項(xiàng)目的性能優(yōu)化。因此在學(xué)習(xí)他們的過(guò)程中,應(yīng)該結(jié)合實(shí)踐去思考優(yōu)化策略。
不過(guò)性能優(yōu)化的方式在我們之前的章節(jié)中已經(jīng)詳細(xì)介紹過(guò),因此這里帶大家閱讀源碼更多的是做一個(gè)驗(yàn)證,去揭開(kāi)源碼的神秘面紗。
到這篇文章這里,React 原理的大多數(shù)重要邏輯我們?cè)谥车奈恼露家呀?jīng)給大家分享過(guò)了,其中包括同步更新邏輯,異步更新邏輯,任務(wù)優(yōu)先級(jí)隊(duì)列,任務(wù)調(diào)度,F(xiàn)iber 中的各種鏈表結(jié)構(gòu),各種比較方式的成本,包括本文介紹的 Fiber tree 的構(gòu)建過(guò)程,大家可以把這些零散的文章串起來(lái)總結(jié)一下,有能力的可以自己在閱讀源碼時(shí)結(jié)合我分享的內(nèi)容進(jìn)一步擴(kuò)展和完善。
閱讀源碼是一個(gè)高投入,低回報(bào)的過(guò)程,希望我的這些文章能有效幫助大家以更低的時(shí)間成本獲得更高的知識(shí)回報(bào)。