renderRoot
1 )概述
-
renderRoot
是一個(gè)非常復(fù)雜的方法 - 這個(gè)方法里處理很多各種各樣的邏輯, 它主要的工作內(nèi)容是什么?
- A. 它調(diào)用
workLoop
進(jìn)行循環(huán)單元更新- 遍歷整個(gè) Fiber Tree,把每一個(gè)組件或者 dom 節(jié)點(diǎn)對(duì)應(yīng)的
- Fiber 節(jié)點(diǎn)拿出來單一的進(jìn)行更新,這是一個(gè)循環(huán)的操作
- 把整棵 Fiber Tree 都遍歷一遍,這就是
workLoop
- B. 捕獲錯(cuò)誤并進(jìn)行處理
- 在進(jìn)行每一個(gè)單元更新的時(shí)候,這個(gè)遍歷邏輯的時(shí)候,有可能會(huì)出現(xiàn)一些錯(cuò)誤
- 有些是可預(yù)期的,比如說是 Suspense 的功能, throw 一個(gè) Promise 對(duì)象
- 這個(gè)時(shí)候,是我們要特殊的對(duì)它進(jìn)行處理的
- 有一些是不可預(yù)期的,比如說, render 里面報(bào)了一個(gè)錯(cuò)誤
- 我們也要對(duì)它進(jìn)行一些處理,要告訴我們的react這個(gè)地方
- 我們出現(xiàn)了一個(gè)錯(cuò)誤,在react16之后,有了
Error Boundary
- 可以在組件內(nèi)捕獲渲染的錯(cuò)誤
- C. 在走完流程之后要進(jìn)行善后
- 因?yàn)榱鞒套咄曛髸?huì)有各種不同的情況
- 比如說有錯(cuò)誤的情況,比如說有任務(wù)被掛起的情況,也就是Suspense的情況
- 這些任務(wù),都要按照特定的邏輯給它進(jìn)行一些處理
- 這就是
renderRoot
這個(gè)方法,它的主要的核心工作內(nèi)容
2 )流程圖

- 進(jìn)入 renderRoot, 它里面有一個(gè)很核心的循環(huán)
do while workLoop
- 這個(gè)while循環(huán)就是調(diào)用 workLoop 對(duì)整棵樹,它每個(gè)節(jié)點(diǎn)進(jìn)行一個(gè)遍歷,并且拿出來單獨(dú)進(jìn)行更新
- 因?yàn)槊總€(gè) Fiber節(jié)點(diǎn)上,如果有更新的話,它會(huì)記入 updateQueen
- 我們通過 updateQueen 上是否有內(nèi)容來判斷它是否要進(jìn)行更新
- 以及可以計(jì)算出它的新的 state,得到最新的 children,拿到所有最新的節(jié)點(diǎn)
- 在 workLoop 的過程當(dāng)中,它在做什么呢?
-
nextUnitOfWork
就是每一個(gè)節(jié)點(diǎn)在遍歷的過程當(dāng)中,它自己更新完之后,它會(huì)返回它的第一個(gè)child - 它的第一個(gè)child 就是作為 nextUnitOfWork,因?yàn)槲覀儓?zhí)行了一個(gè)節(jié)點(diǎn)的更新之后,我們需要返回
- 返回之后,我們要判斷一些邏輯
- 比如,對(duì)于異步的操作,每個(gè)節(jié)點(diǎn)更新完之后都要判斷
!shouldYield()
- 判斷我們現(xiàn)在的時(shí)間片是否還有?如果還有的話,再繼續(xù),如果沒有的話,就要跳出了
- 接下去就會(huì)執(zhí)行
performUnitOfWork
- 之后,執(zhí)行
beginWork
,completeUnitOfWork
這些,當(dāng)然中間會(huì)判斷是否有 next - next就是我在更新完一個(gè)結(jié)點(diǎn)之后,它是否還有下一個(gè)節(jié)點(diǎn)需要更新
- 如果有next的情況,我們就返回,然后去判斷這個(gè)邏輯是否還有
- 這就是整個(gè)對(duì)整個(gè) fiber tree 每個(gè)節(jié)點(diǎn)的遍歷更新
- 在這個(gè)更新過程當(dāng)中,如果有任何catch,就是捕獲到異常
- 那么首先會(huì)進(jìn)行一系列的判斷,然后對(duì)它執(zhí)行 throwException 或者是 onUncaughtError
- 它們對(duì)應(yīng)的邏輯會(huì)不一樣, 如果這個(gè)節(jié)點(diǎn)它是可處理的錯(cuò)誤
- 我會(huì)直接對(duì)它進(jìn)行
completeUnitOfWork()
因?yàn)楦碌竭@個(gè)節(jié)點(diǎn)之后,它拋出錯(cuò)誤了 - 說明我們這個(gè)節(jié)點(diǎn)下面的所有子節(jié)點(diǎn)都不需要再更新了
- 執(zhí)行完成之后,我們就會(huì)調(diào)用 continue,對(duì)于這個(gè) do while 循環(huán),它又繼續(xù)調(diào)用 workLoop
- 也就是說我們把一棵子數(shù)的錯(cuò)誤處理完之后,它還可以繼續(xù)對(duì)別的子樹進(jìn)行更新
- 整體更新完之后,就會(huì) break,之后會(huì)有各種不同的情況,比如說有致命的錯(cuò)誤等
- 它們都會(huì)調(diào)用不同的邏輯進(jìn)行一個(gè)處理,這里
nextRenderDidError
是一個(gè)可處理的錯(cuò)誤 - 比如說, 是可以被捕獲的錯(cuò)誤,有組件能捕獲它,而
nextLatestAbsoluteTimeoutMs
是拋出promise的錯(cuò)誤 - 它是一個(gè)被掛起的一個(gè)任務(wù),對(duì)應(yīng)要執(zhí)行一個(gè)被被掛起的操作, 最后如果上面的情況都沒有出現(xiàn)
- 直接
onComplete
,之后就可以在root節(jié)點(diǎn)上set finishedwork
,這樣, 就可以對(duì)它整體進(jìn)行一個(gè)更新 - 就可以執(zhí)行
completeRoot
,就可以把 fiber樹 變成我們真正的dom 樹去更新整個(gè)頁面 - 這就是整個(gè) renderRoot 它的一個(gè)邏輯,簡單總結(jié)
- 先正常的執(zhí)行每個(gè)單元的更新
- 然后捕獲到任何錯(cuò)誤進(jìn)行一定的處理
- 最終把整個(gè)樹遍歷完之后根據(jù)不同的情況再進(jìn)行一個(gè)處理
3 )源碼
定位到 packages/react-reconciler/src/ReactFiberScheduler.js
先看 renderRoot 的代碼
function renderRoot(
root: FiberRoot,
isYieldy: boolean,
isExpired: boolean,
): void {
invariant(
!isWorking,
'renderRoot was called recursively. This error is likely caused ' +
'by a bug in React. Please file an issue.',
);
isWorking = true;
ReactCurrentOwner.currentDispatcher = Dispatcher;
const expirationTime = root.nextExpirationTimeToWorkOn;
// Check if we're starting from a fresh stack, or if we're resuming from
// previously yielded work.
// 剛進(jìn)來的時(shí)候,進(jìn)行這個(gè)判斷
// nextRoot 和 nextRenderExpirationTime 對(duì)應(yīng)著接下來要渲染的節(jié)點(diǎn)和對(duì)應(yīng)的過期時(shí)間 這是兩個(gè)公共變量
// 在這種情況下,說明調(diào)用這個(gè)方法的時(shí)候,接收到的參數(shù)和之前的不一樣,可能就是之前的異步任務(wù)被新進(jìn)來的高優(yōu)先級(jí)的任務(wù)給打斷了
if (
expirationTime !== nextRenderExpirationTime ||
root !== nextRoot ||
nextUnitOfWork === null
) {
// Reset the stack and start working from the root.
resetStack();
nextRoot = root;
nextRenderExpirationTime = expirationTime;
// nextUnitOfWork 來自于 createWorkInProgress
// 就是把當(dāng)前的應(yīng)用的狀態(tài)對(duì)應(yīng)的Fiber節(jié)點(diǎn),拷貝了一份叫做 workInProgress 的對(duì)象
// 因?yàn)槲覀儾荒苤苯釉诋?dāng)前對(duì)象的Fiber節(jié)點(diǎn)上操作,它會(huì)影響我們目前的dom節(jié)點(diǎn)展示的樣子
// 所以要復(fù)制一份拷貝,對(duì)拷貝進(jìn)行操作,workInProgress 和 current 之間會(huì)有一個(gè)轉(zhuǎn)換的關(guān)系
// 在renderRoot開始之后,我們真正操作的節(jié)點(diǎn)都是 workInProgress,沒有直接在 current 上操作
nextUnitOfWork = createWorkInProgress(
nextRoot.current,
null,
nextRenderExpirationTime,
);
// 這種和不同expirationTime會(huì)有關(guān)系
root.pendingCommitExpirationTime = NoWork;
if (enableSchedulerTracing) {
// Determine which interactions this batch of work currently includes,
// So that we can accurately attribute time spent working on it,
// And so that cascading work triggered during the render phase will be associated with it.
const interactions: Set<Interaction> = new Set();
root.pendingInteractionMap.forEach(
(scheduledInteractions, scheduledExpirationTime) => {
if (scheduledExpirationTime <= expirationTime) {
scheduledInteractions.forEach(interaction =>
interactions.add(interaction),
);
}
},
);
// Store the current set of interactions on the FiberRoot for a few reasons:
// We can re-use it in hot functions like renderRoot() without having to recalculate it.
// We will also use it in commitWork() to pass to any Profiler onRender() hooks.
// This also provides DevTools with a way to access it when the onCommitRoot() hook is called.
root.memoizedInteractions = interactions;
if (interactions.size > 0) {
const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(
expirationTime,
root.interactionThreadID,
);
try {
subscriber.onWorkStarted(interactions, threadID);
} catch (error) {
// Work thrown by an interaction tracing subscriber should be rethrown,
// But only once it's safe (to avoid leaveing the scheduler in an invalid state).
// Store the error for now and we'll re-throw in finishRendering().
if (!hasUnhandledError) {
hasUnhandledError = true;
unhandledError = error;
}
}
}
}
}
}
let prevInteractions: Set<Interaction> = (null: any);
if (enableSchedulerTracing) {
// We're about to start new traced work.
// Restore pending interactions so cascading work triggered during the render phase will be accounted for.
prevInteractions = __interactionsRef.current;
__interactionsRef.current = root.memoizedInteractions;
}
let didFatal = false;
startWorkLoopTimer(nextUnitOfWork);
// 上面初始化工作做完之后,就開始 workLoop
// 如果有 catch 就會(huì)有一大段處理邏輯, 這里先跳過
//
do {
try {
workLoop(isYieldy);
} catch (thrownValue) {
if (nextUnitOfWork === null) {
// This is a fatal error.
didFatal = true;
onUncaughtError(thrownValue);
} else {
if (__DEV__) {
// Reset global debug state
// We assume this is defined in DEV
(resetCurrentlyProcessingQueue: any)();
}
const failedUnitOfWork: Fiber = nextUnitOfWork;
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
}
// TODO: we already know this isn't true in some cases.
// At least this shows a nicer error message until we figure out the cause.
// https://github.com/facebook/react/issues/12449#issuecomment-386727431
invariant(
nextUnitOfWork !== null,
'Failed to replay rendering after an error. This ' +
'is likely caused by a bug in React. Please file an issue ' +
'with a reproducing case to help us find it.',
);
const sourceFiber: Fiber = nextUnitOfWork;
let returnFiber = sourceFiber.return;
if (returnFiber === null) {
// This is the root. The root could capture its own errors. However,
// we don't know if it errors before or after we pushed the host
// context. This information is needed to avoid a stack mismatch.
// Because we're not sure, treat this as a fatal error. We could track
// which phase it fails in, but doesn't seem worth it. At least
// for now.
didFatal = true;
onUncaughtError(thrownValue);
} else {
throwException(
root,
returnFiber,
sourceFiber,
thrownValue,
nextRenderExpirationTime,
);
nextUnitOfWork = completeUnitOfWork(sourceFiber);
continue;
}
}
}
break;
} while (true);
if (enableSchedulerTracing) {
// Traced work is done for now; restore the previous interactions.
__interactionsRef.current = prevInteractions;
}
// We're done performing work. Time to clean up.
isWorking = false;
ReactCurrentOwner.currentDispatcher = null;
resetContextDependences();
// 在處理完 workLoop 后這里會(huì)有各種不同的判斷
// Yield back to main thread.
// 這里代表有致命的錯(cuò)誤
if (didFatal) {
const didCompleteRoot = false;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
interruptedBy = null;
// There was a fatal error.
if (__DEV__) {
resetStackAfterFatalErrorInDev();
}
// `nextRoot` points to the in-progress root. A non-null value indicates
// that we're in the middle of an async render. Set it to null to indicate
// there's no more work to be done in the current batch.
nextRoot = null;
onFatal(root);
return;
}
// 正常流程走完,這個(gè)if一定會(huì)匹配
// 因?yàn)橐呀?jīng)跳出 workLoop 了,說明一定有 react沒有意識(shí)到的錯(cuò)誤,所以調(diào)用 onYield
if (nextUnitOfWork !== null) {
// There's still remaining async work in this tree, but we ran out of time
// in the current frame. Yield back to the renderer. Unless we're
// interrupted by a higher priority update, we'll continue later from where
// we left off.
const didCompleteRoot = false;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
interruptedBy = null;
onYield(root);
return;
}
// We completed the whole tree.
const didCompleteRoot = true;
stopWorkLoopTimer(interruptedBy, didCompleteRoot);
const rootWorkInProgress = root.current.alternate;
invariant(
rootWorkInProgress !== null,
'Finished root should have a work-in-progress. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// `nextRoot` points to the in-progress root. A non-null value indicates
// that we're in the middle of an async render. Set it to null to indicate
// there's no more work to be done in the current batch.
nextRoot = null;
interruptedBy = null;
// 這里也是
if (nextRenderDidError) {
// There was an error
if (hasLowerPriorityWork(root, expirationTime)) {
// There's lower priority work. If so, it may have the effect of fixing
// the exception that was just thrown. Exit without committing. This is
// similar to a suspend, but without a timeout because we're not waiting
// for a promise to resolve. React will restart at the lower
// priority level.
markSuspendedPriorityLevel(root, expirationTime);
const suspendedExpirationTime = expirationTime;
const rootExpirationTime = root.expirationTime;
onSuspend(
root,
rootWorkInProgress,
suspendedExpirationTime,
rootExpirationTime,
-1, // Indicates no timeout
);
return;
} else if (
// There's no lower priority work, but we're rendering asynchronously.
// Synchronsouly attempt to render the same level one more time. This is
// similar to a suspend, but without a timeout because we're not waiting
// for a promise to resolve.
!root.didError &&
!isExpired
) {
root.didError = true;
const suspendedExpirationTime = (root.nextExpirationTimeToWorkOn = expirationTime);
const rootExpirationTime = (root.expirationTime = Sync);
onSuspend(
root,
rootWorkInProgress,
suspendedExpirationTime,
rootExpirationTime,
-1, // Indicates no timeout
);
return;
}
}
// 注意這里的錯(cuò)誤
if (!isExpired && nextLatestAbsoluteTimeoutMs !== -1) {
// The tree was suspended.
const suspendedExpirationTime = expirationTime;
markSuspendedPriorityLevel(root, suspendedExpirationTime);
// Find the earliest uncommitted expiration time in the tree, including
// work that is suspended. The timeout threshold cannot be longer than
// the overall expiration.
const earliestExpirationTime = findEarliestOutstandingPriorityLevel(
root,
expirationTime,
);
const earliestExpirationTimeMs = expirationTimeToMs(earliestExpirationTime);
if (earliestExpirationTimeMs < nextLatestAbsoluteTimeoutMs) {
nextLatestAbsoluteTimeoutMs = earliestExpirationTimeMs;
}
// Subtract the current time from the absolute timeout to get the number
// of milliseconds until the timeout. In other words, convert an absolute
// timestamp to a relative time. This is the value that is passed
// to `setTimeout`.
const currentTimeMs = expirationTimeToMs(requestCurrentTime());
let msUntilTimeout = nextLatestAbsoluteTimeoutMs - currentTimeMs;
msUntilTimeout = msUntilTimeout < 0 ? 0 : msUntilTimeout;
// TODO: Account for the Just Noticeable Difference
const rootExpirationTime = root.expirationTime;
onSuspend(
root,
rootWorkInProgress,
suspendedExpirationTime,
rootExpirationTime,
msUntilTimeout,
);
return;
}
// Ready to commit.
onComplete(root, rootWorkInProgress, expirationTime);
}
- renderRoot 代碼會(huì)相對(duì)比較長,要把代碼的區(qū)塊進(jìn)行一個(gè)區(qū)分
- 一些原版英文注釋,和我添加的中文注釋如上
現(xiàn)在來看下 workLoop 的源碼文章來源:http://www.zghlxwxcb.cn/news/detail-797722.html
function workLoop(isYieldy) {
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until the deadline runs out of time.
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
- 它接收一個(gè) isYieldy 作為參數(shù)
- 這個(gè)參數(shù)意味著是否可以被中斷
- Sync的任務(wù)和已超時(shí)的異步任務(wù)都是不可中斷的
- 如果是不可中斷的,只要有
nextUnitOfWork
- 就會(huì)繼續(xù)調(diào)用
performUnitOfWork
- 如果是可以中斷的,就通過判斷
!shouldYield()
- 來看當(dāng)前時(shí)間片中是否還有足夠的時(shí)間繼續(xù)渲染下一個(gè)節(jié)點(diǎn)
再來看下 performUnitOfWork文章來源地址http://www.zghlxwxcb.cn/news/detail-797722.html
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
// The current, flushed, state of this fiber is the alternate.
// Ideally nothing should rely on this, but relying on it here
// means that we don't need an additional field on the work in
// progress.
const current = workInProgress.alternate;
// See if beginning this work spawns more work.
startWorkTimer(workInProgress);
if (__DEV__) {
ReactCurrentFiber.setCurrentFiber(workInProgress);
}
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
stashedWorkInProgressProperties = assignFiberPropertiesInDEV(
stashedWorkInProgressProperties,
workInProgress,
);
}
let next;
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
if (workInProgress.mode & ProfileMode) {
// Record the render duration assuming we didn't bailout (or error).
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
}
} else {
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;
}
if (__DEV__) {
ReactCurrentFiber.resetCurrentFiber();
if (isReplayingFailedUnitOfWork) {
// Currently replaying a failed unit of work. This should be unreachable,
// because the render phase is meant to be idempotent, and it should
// have thrown again. Since it didn't, rethrow the original error, so
// React's internal stack is not misaligned.
rethrowOriginalError();
}
}
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onBeginWork(workInProgress);
}
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner.current = null;
return next;
}
- 它聲明了一個(gè) next 變量,next = beginWork(…), 這里涉及到對(duì)每個(gè)節(jié)點(diǎn)的更新
- 更新完一個(gè)節(jié)點(diǎn)之后,它會(huì)返回它的下一個(gè)節(jié)點(diǎn)
- 會(huì)更新 workInProgress.memoizedProps, 節(jié)點(diǎn)已經(jīng)更新完了
- 最新的 props 已經(jīng)變成目前正在用的 props
- 先跳過
- 跳過 DEV 的代碼
- 如果 next === null 說明這個(gè)節(jié)點(diǎn)已經(jīng)更新到子樹的葉子節(jié)點(diǎn)了
- 這棵子樹就可以結(jié)束了
- 結(jié)束就調(diào)用 completeUnitOfWork
- 它也會(huì)返回它的下一個(gè)節(jié)點(diǎn)
- 最后,return next
- 在 workLoop 函數(shù)中可看到,它會(huì)賦值給 nextUnitOfWork
// 參考其中一個(gè) while while (nextUnitOfWork !== null) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); }
- 所以,真正到 nextUnitOfWork 為 null 的情況是它到了根節(jié)點(diǎn),即 FiberRoot 節(jié)點(diǎn)
- 它的 return 是 null,這時(shí)就跳出了 while 循環(huán)了
- 在 workLoop 函數(shù)中可看到,它會(huì)賦值給 nextUnitOfWork
到了這里,關(guān)于React16源碼: React中的renderRoot的源碼實(shí)現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!