前言:
因爲我的能力和精力有限,外加 renderRoot 的內容太多,裏面的每一個 function 均可以單獨拿出來說,因此本篇文章的目的是幫助你們瞭解 renderRoot 大概作了哪些事,而這些事具體的內容,之後會一個個單獨拿出來解析。javascript
在哪裏用到
在 React源碼解析之scheduleWork(上)中:php
export function scheduleUpdateOnFiber(){
xxx
xxx
xxx
if (expirationTime === Sync) {
if( 第一次render ){
xxx
//初始化root,調用workLoop進行循環單元更新
let callback = renderRoot(root, Sync, true);
}else{
xxx
}
}
}
複製代碼
1、renderRoot
主要的做用:
(1) 調用 workLoop 進行循環單元更新
(2) 捕獲錯誤並進行處理
(3) 走完流程後,根據workInProgressRoot
的不一樣狀態來進行不一樣的操做java
源碼:react
function renderRoot(
root: FiberRoot,
expirationTime: ExpirationTime,
isSync: boolean,
): SchedulerCallback | null {
invariant(
(executionContext & (RenderContext | CommitContext)) === NoContext,
'Should not already be working.',
);
if (enableUserTimingAPI && expirationTime !== Sync) {
const didExpire = isSync;
stopRequestCallbackTimer(didExpire);
}
if (root.firstPendingTime < expirationTime) {
// If there's no work left at this expiration time, exit immediately. This
// happens when multiple callbacks are scheduled for a single root, but an
// earlier callback flushes the work of a later one.
return null;
}
if (isSync && root.finishedExpirationTime === expirationTime) {
// There's already a pending commit at this expiration time.
// TODO: This is poorly factored. This case only exists for the
// batch.commit() API.
return commitRoot.bind(null, root);
}
// 據說是useEffect的調用
flushPassiveEffects();
// If the root or expiration time have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
/*nextRoot =》 workInProgressRoot*/
/*nextRenderExpirationTime =》 renderExpirationTime*/
//workInProgressRoot 指接下來要更新的節點
//renderExpirationTime 指接下來更新節點的過時時間
//意思就是當前要更新的節點並不是是隊列中要更新的節點,也就是說被新的高優先級的任務給打斷了
if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
//重置調度隊列,並從root節點(新的高優先級的節點)開始調度
/*resetStack <=> prepareFreshStack */
prepareFreshStack(root, expirationTime);
//將調度優先級高的interaction加入到interactions中
startWorkOnPendingInteractions(root, expirationTime);
}
//應該是當已經接收一個低優先級的要更新的節點時所進行的操做
else if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// We could've received an update at a lower priority while we yielded.
// We're suspended in a delayed state. Once we complete this render we're
// just going to try to recover at the last pending time anyway so we might
// as well start doing that eagerly.
// Ideally we should be able to do this even for retries but we don't yet
// know if we're going to process an update which wants to commit earlier,
// and this path happens very early so it would happen too often. Instead,
// for that case, we'll wait until we complete.
if (workInProgressRootHasPendingPing) {
// We have a ping at this expiration. Let's restart to see if we get unblocked.
prepareFreshStack(root, expirationTime);
} else {
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime < expirationTime) {
// There's lower priority work. It might be unsuspended. Try rendering
// at that level immediately, while preserving the position in the queue.
return renderRoot.bind(null, root, lastPendingTime);
}
}
}
// If we have a work-in-progress fiber, it means there's still work to do
// in this root.
if (workInProgress !== null) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
let prevDispatcher = ReactCurrentDispatcher.current;
if (prevDispatcher === null) {
// The React isomorphic package does not include a default dispatcher.
// Instead the first renderer will lazily attach one, in order to give
// nicer error messages.
prevDispatcher = ContextOnlyDispatcher;
}
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
let prevInteractions: Set<Interaction> | null = null;
if (enableSchedulerTracing) {
prevInteractions = __interactionsRef.current;
__interactionsRef.current = root.memoizedInteractions;
}
//綁定 currentFiber,也標誌着開始執行 workloop
startWorkLoopTimer(workInProgress);
// TODO: Fork renderRoot into renderRootSync and renderRootAsync
//若是是同步的話
if (isSync) {
//若是更新時間是異步的話
if (expirationTime !== Sync) {
// An async update expired. There may be other expired updates on
// this root. We should render all the expired work in a
// single batch.
//將全部過時的時間分批次處理
const currentTime = requestCurrentTime();
if (currentTime < expirationTime) {
// Restart at the current time.
executionContext = prevExecutionContext;
resetContextDependencies();
ReactCurrentDispatcher.current = prevDispatcher;
if (enableSchedulerTracing) {
__interactionsRef.current = ((prevInteractions: any): Set<
Interaction,
>);
}
return renderRoot.bind(null, root, currentTime);
}
}
} else {
// Since we know we're in a React event, we can clear the current
// event time. The next update will compute a new event time.
//清除currentEventTime
currentEventTime = NoWork;
}
do {
try {
//執行每一個節點的更新
if (isSync) {
workLoopSync();
} else {
//判斷是否須要繼續調用performUnitOfWork
workLoop();
}
break;
}
//捕獲異常,並處理
catch (thrownValue)
{
// Reset module-level state that was set during the render phase.
//重置狀態
resetContextDependencies();
resetHooks();
const sourceFiber = workInProgress;
/*nextUnitOfWork <=> sourceFiber*/
//若是sourceFiber是存在的,那麼 React 能夠判斷錯誤的緣由
//若是sourceFiber是不存在的,說明是未知錯誤
if (sourceFiber === null || sourceFiber.return === null) {
// Expected to be working on a non-root fiber. This is a fatal error
// because there's no ancestor that can handle it; the root is
// supposed to capture all errors that weren't caught by an error
// boundary.
//重置調度隊列,並從root節點(新的高優先級的節點)開始調度
prepareFreshStack(root, expirationTime);
executionContext = prevExecutionContext;
//拋出錯誤
throw thrownValue;
}
//記錄error被捕獲前,渲染所花費的時間
//這樣能夠避免在渲染掛起(暫停)的狀況下,Profiler的時間會不許確
//Profiler:測量渲染一個 React 應用多久渲染一次以及渲染一次的「代價」。
//它的目的是識別出應用中渲染較慢的部分,或是能夠使用相似 memoization 優化的部分,並從相關優化中獲益。
if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {
// Record the time spent rendering before an error was thrown. This
// avoids inaccurate Profiler durations in the case of a
// suspended render.
stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);
}
//獲取父節點
const returnFiber = sourceFiber.return;
//拋出可預期的錯誤
throwException(
root,
returnFiber,
sourceFiber,
thrownValue,
renderExpirationTime,
);
//完成對sourceFiber的渲染,
//可是由於已是報錯的,因此不會再渲染sourceFiber的子節點了
workInProgress = completeUnitOfWork(sourceFiber);
}
} while (true);
executionContext = prevExecutionContext;
//重置狀態
resetContextDependencies();
ReactCurrentDispatcher.current = prevDispatcher;
if (enableSchedulerTracing) {
__interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
}
//若是仍有正在進程裏的任務
if (workInProgress !== null) {
// There's still work left over. Return a continuation.
//中止計時
stopInterruptedWorkLoopTimer();
if (expirationTime !== Sync) {
//開始調度callback的標誌
startRequestCallbackTimer();
}
//綁定 this
return renderRoot.bind(null, root, expirationTime);
}
}
// We now have a consistent tree. The next step is either to commit it, or, if
// something suspended, wait to commit it after a timeout.
// 至此,保證了 fiber 樹的每一個節點的狀態都是一致的。接下來會執行 commit 步驟/或者是又有新的任務被掛起了,等待掛起結束再去 commit
stopFinishedWorkLoopTimer();
root.finishedWork = root.current.alternate;
root.finishedExpirationTime = expirationTime;
//判斷當前節點是否被阻止commit
const isLocked = resolveLocksOnRoot(root, expirationTime);
//若是有,則退出
if (isLocked) {
// This root has a lock that prevents it from committing. Exit. If we begin
// work on the root again, without any intervening updates, it will finish
// without doing additional work.
return null;
}
// Set this to null to indicate there's no in-progress render.
//將workInProgressRoot以告訴 react 沒有正在 render 的進程
workInProgressRoot = null;
//根據workInProgressRoot的不一樣狀態來進行不一樣的操做
switch (workInProgressRootExitStatus) {
case RootIncomplete: {
invariant(false, 'Should have a work-in-progress.');
}
// Flow knows about invariant, so it compains if I add a break statement,
// but eslint doesn't know about invariant, so it complains if I do.
//對下面 eslint 註釋的解釋,可不看
// eslint-disable-next-line no-fallthrough
case RootErrored: {
// An error was thrown. First check if there is lower priority work
// scheduled on this root.
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime < expirationTime) {
// There's lower priority work. Before raising the error, try rendering
// at the lower priority to see if it fixes it. Use a continuation to
// maintain the existing priority and position in the queue.
return renderRoot.bind(null, root, lastPendingTime);
}
if (!isSync) {
// If we're rendering asynchronously, it's possible the error was
// caused by tearing due to a mutation during an event. Try rendering
// one more time without yiedling to events.
prepareFreshStack(root, expirationTime);
scheduleSyncCallback(renderRoot.bind(null, root, expirationTime));
return null;
}
// If we're already rendering synchronously, commit the root in its
// errored state.
return commitRoot.bind(null, root);
}
case RootSuspended: {
// We have an acceptable loading state. We need to figure out if we should
// immediately commit it or wait a bit.
// If we have processed new updates during this render, we may now have a
// new loading state ready. We want to ensure that we commit that as soon as
// possible.
const hasNotProcessedNewUpdates =
workInProgressRootLatestProcessedExpirationTime === Sync;
if (hasNotProcessedNewUpdates && !isSync) {
// If we have not processed any new updates during this pass, then this is
// either a retry of an existing fallback state or a hidden tree.
// Hidden trees shouldn't be batched with other work and after that's
// fixed it can only be a retry.
// We're going to throttle committing retries so that we don't show too
// many loading states too quickly.
let msUntilTimeout =
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
// Don't bother with a very short suspense time.
if (msUntilTimeout > 10) {
if (workInProgressRootHasPendingPing) {
// This render was pinged but we didn't get to restart earlier so try
// restarting now instead.
prepareFreshStack(root, expirationTime);
return renderRoot.bind(null, root, expirationTime);
}
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime < expirationTime) {
// There's lower priority work. It might be unsuspended. Try rendering
// at that level.
return renderRoot.bind(null, root, lastPendingTime);
}
// The render is suspended, it hasn't timed out, and there's no lower
// priority work to do. Instead of committing the fallback
// immediately, wait for more data to arrive.
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
);
return null;
}
}
// The work expired. Commit immediately.
return commitRoot.bind(null, root);
}
case RootSuspendedWithDelay: {
if (!isSync) {
// We're suspended in a state that should be avoided. We'll try to avoid committing
// it for as long as the timeouts let us.
if (workInProgressRootHasPendingPing) {
// This render was pinged but we didn't get to restart earlier so try
// restarting now instead.
prepareFreshStack(root, expirationTime);
return renderRoot.bind(null, root, expirationTime);
}
const lastPendingTime = root.lastPendingTime;
if (lastPendingTime < expirationTime) {
// There's lower priority work. It might be unsuspended. Try rendering
// at that level immediately.
return renderRoot.bind(null, root, lastPendingTime);
}
let msUntilTimeout;
if (workInProgressRootLatestSuspenseTimeout !== Sync) {
// We have processed a suspense config whose expiration time we can use as
// the timeout.
msUntilTimeout =
expirationTimeToMs(workInProgressRootLatestSuspenseTimeout) - now();
} else if (workInProgressRootLatestProcessedExpirationTime === Sync) {
// This should never normally happen because only new updates cause
// delayed states, so we should have processed something. However,
// this could also happen in an offscreen tree.
msUntilTimeout = 0;
} else {
// If we don't have a suspense config, we're going to use a heuristic to
// determine how long we can suspend.
const eventTimeMs: number = inferTimeFromExpirationTime(
workInProgressRootLatestProcessedExpirationTime,
);
const currentTimeMs = now();
const timeUntilExpirationMs =
expirationTimeToMs(expirationTime) - currentTimeMs;
let timeElapsed = currentTimeMs - eventTimeMs;
if (timeElapsed < 0) {
// We get this wrong some time since we estimate the time.
timeElapsed = 0;
}
msUntilTimeout = jnd(timeElapsed) - timeElapsed;
// Clamp the timeout to the expiration time.
// TODO: Once the event time is exact instead of inferred from expiration time
// we don't need this.
if (timeUntilExpirationMs < msUntilTimeout) {
msUntilTimeout = timeUntilExpirationMs;
}
}
// Don't bother with a very short suspense time.
if (msUntilTimeout > 10) {
// The render is suspended, it hasn't timed out, and there's no lower
// priority work to do. Instead of committing the fallback
// immediately, wait for more data to arrive.
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
);
return null;
}
}
// The work expired. Commit immediately.
return commitRoot.bind(null, root);
}
case RootCompleted: {
// The work completed. Ready to commit.
if (
!isSync &&
workInProgressRootLatestProcessedExpirationTime !== Sync &&
workInProgressRootCanSuspendUsingConfig !== null
) {
// If we have exceeded the minimum loading delay, which probably
// means we have shown a spinner already, we might have to suspend
// a bit longer to ensure that the spinner is shown for enough time.
const msUntilTimeout = computeMsUntilSuspenseLoadingDelay(
workInProgressRootLatestProcessedExpirationTime,
expirationTime,
workInProgressRootCanSuspendUsingConfig,
);
if (msUntilTimeout > 10) {
root.timeoutHandle = scheduleTimeout(
commitRoot.bind(null, root),
msUntilTimeout,
);
return null;
}
}
return commitRoot.bind(null, root);
}
default: {
invariant(false, 'Unknown root exit status.');
}
}
}
複製代碼
解析:
(1) 前面的三個if
和flushPassiveEffects()
不用去看git
(2) 若是render
的時候,有個更高優先級的任務插進來要執行的話,需執行prepareFreshStack
,重置調度隊列,並從root
節點(新的高優先級的節點)開始調度github
(3) else if (workInProgressRootExitStatus === RootSuspendedWithDelay)
的部分不看,應該是當已經接收一個低優先級的要更新的節點時所進行的操做web
(4) 當該節點上面仍有未執行的任務時,執行startWorkLoopTimer()
,綁定currentFiber
,也標誌着開始執行workloop
app
(5) if (isSync) { xxx } else { xxx }
可不看異步
(6) 調用workLoop()/workLoopSync()
進行循環單元的更新,這個方法之後會詳細解析async
(7) 捕獲異常,並處理,重點是throwException
方法,用來拋出可預期的錯誤,之後解析
(8) 根據workInProgressRoot
的不一樣狀態來進行不一樣的操做,也就是當root
被處理完,根據它的狀態,來判斷後續是進行commit
、阻止commit
並從新render
、等待一段時間再去commit
、仍是報錯。
GitHub:
github.com/AttackXiaoJ…
(完)