在哪里用到 在 React源码解析之scheduleWork(上)中:
export function scheduleUpdateOnFiber(){
xxx
xxx
xxx
if (expirationTime === Sync) {
if( 第一次render ){
xxx
//初始化root,调用workLoop进行循环单元更新
let callback = renderRoot(root, Sync, true);
}else{
xxx
}
}
}
一、renderRoot
主要的作用:
(1) 调用 workLoop 进行循环单元更新
(2) 捕获错误并进行处理
(3) 走完流程后,根据workInProgressRoot
的不同状态来进行不同的操作
源码:
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()
不用去看
(2) 如果render
的时候,有个更高优先级的任务插进来要执行的话,需执行prepareFreshStack
,重置调度队列,并从root
节点(新的高优先级的节点)开始调度
(3) else if (workInProgressRootExitStatus === RootSuspendedWithDelay)
的部分不看,应该是当已经接收一个低优先级的要更新的节点时所进行的操作
(4) 当该节点上面仍有未执行的任务时,执行startWorkLoopTimer()
,绑定currentFiber
,也标志着开始执行workloop
(5) if (isSync) { xxx } else { xxx }
可不看
(6) 调用workLoop()/workLoopSync()
进行循环单元的更新,这个方法以后会详细解析
(7) 捕获异常,并处理,重点是throwException
方法,用来抛出可预期的错误,以后解析
(8) 根据workInProgressRoot
的不同状态来进行不同的操作,也就是当root
被处理完,根据它的状态,来判断后续是进行commit
、阻止commit
并重新render
、等待一段时间再去commit
、还是报错。
GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js