Fiber 工作循环?循环工作? Who care?
一、作用
二、导出的常量
1. 上下文常量
备注
源码中的 408 - 411 行
// 无上下文
export const NoContext = 0b000;
// 渲染上下文
export const RenderContext = 0b010;
// 提交上下文
export const CommitContext = 0b100;
2. 交叉渲染 Lane
备注
源码中的 464 - 472 行
// A contextual version of workInProgressRootRenderLanes. It is a superset of
// the lanes that we started working on at the root. When we enter a subtree
// that is currently hidden, we add the lanes that would have committed if
// the hidden tree hadn't been deferred. This is modified by the
// HiddenContext module.
//
// workInProgressRootRenderLanes 的上下文版本。
// 它是我们在根节点开始工作的车道的超集。
// 当我们进入一个当前被隐藏的子树时,我们会添加如果隐藏树没有被延迟而会提交的车道。
// 这会被 HiddenContext 模块修改。
//
// Most things in the work loop should deal with workInProgressRootRenderLanes.
// Most things in begin/complete phases should deal with entangledRenderLanes.
//
// 工作循环中的大多数内容应该处理 workInProgressRootRenderLanes。
// 开始/完成阶段中的大多数内容应该处理 entangledRenderLanes。
export let entangledRenderLanes: Lanes = NoLanes;
三、 获取进行中的工作转换
export function getWorkInProgressTransitions(): null | Array<Transition> {
return workInProgressTransitions;
}
四、 向待处理过渡添加回调
1. 向待处理过渡添加开始回调
export function addTransitionStartCallbackToPendingTransition(
transition: Transition,
) {
if (enableTransitionTracing) {
if (currentPendingTransitionCallbacks === null) {
currentPendingTransitionCallbacks = {
transitionStart: [],
transitionProgress: null,
transitionComplete: null,
markerProgress: null,
markerIncomplete: null,
markerComplete: null,
};
}
if (currentPendingTransitionCallbacks.transitionStart === null) {
currentPendingTransitionCallbacks.transitionStart =
[] as Array<Transition>;
}
currentPendingTransitionCallbacks.transitionStart.push(transition);
}
}
2. 向待处理过渡添加标记进度回调
export function addMarkerProgressCallbackToPendingTransition(
markerName: string,
transitions: Set<Transition>,
pendingBoundaries: PendingBoundaries,
) {
if (enableTransitionTracing) {
if (currentPendingTransitionCallbacks === null) {
currentPendingTransitionCallbacks = {
transitionStart: null,
transitionProgress: null,
transitionComplete: null,
markerProgress: new Map(),
markerIncomplete: null,
markerComplete: null,
} as PendingTransitionCallbacks;
}
if (currentPendingTransitionCallbacks.markerProgress === null) {
currentPendingTransitionCallbacks.markerProgress = new Map();
}
currentPendingTransitionCallbacks.markerProgress.set(markerName, {
pendingBoundaries,
transitions,
});
}
}
3. 向待处理过渡添加标记不完整回调
export function addMarkerIncompleteCallbackToPendingTransition(
markerName: string,
transitions: Set<Transition>,
aborts: Array<TransitionAbort>,
) {
if (enableTransitionTracing) {
if (currentPendingTransitionCallbacks === null) {
currentPendingTransitionCallbacks = {
transitionStart: null,
transitionProgress: null,
transitionComplete: null,
markerProgress: null,
markerIncomplete: new Map(),
markerComplete: null,
};
}
if (currentPendingTransitionCallbacks.markerIncomplete === null) {
currentPendingTransitionCallbacks.markerIncomplete = new Map();
}
currentPendingTransitionCallbacks.markerIncomplete.set(markerName, {
transitions,
aborts,
});
}
}
4. 向待处理过渡添加标记完成回调
export function addMarkerCompleteCallbackToPendingTransition(
markerName: string,
transitions: Set<Transition>,
) {
if (enableTransitionTracing) {
if (currentPendingTransitionCallbacks === null) {
currentPendingTransitionCallbacks = {
transitionStart: null,
transitionProgress: null,
transitionComplete: null,
markerProgress: null,
markerIncomplete: null,
markerComplete: new Map(),
};
}
if (currentPendingTransitionCallbacks.markerComplete === null) {
currentPendingTransitionCallbacks.markerComplete = new Map();
}
currentPendingTransitionCallbacks.markerComplete.set(
markerName,
transitions,
);
}
}
5. 向待处理过渡添加进度回调
export function addTransitionProgressCallbackToPendingTransition(
transition: Transition,
boundaries: PendingBoundaries,
) {
if (enableTransitionTracing) {
if (currentPendingTransitionCallbacks === null) {
currentPendingTransitionCallbacks = {
transitionStart: null,
transitionProgress: new Map(),
transitionComplete: null,
markerProgress: null,
markerIncomplete: null,
markerComplete: null,
};
}
if (currentPendingTransitionCallbacks.transitionProgress === null) {
currentPendingTransitionCallbacks.transitionProgress = new Map();
}
currentPendingTransitionCallbacks.transitionProgress.set(
transition,
boundaries,
);
}
}
6. 向待处理过渡添加完成回调
export function addTransitionCompleteCallbackToPendingTransition(
transition: Transition,
) {
if (enableTransitionTracing) {
if (currentPendingTransitionCallbacks === null) {
currentPendingTransitionCallbacks = {
transitionStart: null,
transitionProgress: null,
transitionComplete: [],
markerProgress: null,
markerIncomplete: null,
markerComplete: null,
};
}
if (currentPendingTransitionCallbacks.transitionComplete === null) {
currentPendingTransitionCallbacks.transitionComplete =
[] as Array<Transition>;
}
currentPendingTransitionCallbacks.transitionComplete.push(transition);
}
}
五、 获取渲染目标的时间
export function getRenderTargetTime(): number {
return workInProgressRootRenderTargetTime;
}
六、获取变量
1. 获取进行中的根
export function getWorkInProgressRoot(): FiberRoot | null {
return workInProgressRoot;
}
2. 获取提交根
export function getCommittingRoot(): FiberRoot | null {
return pendingEffectsRoot;
}
3. 获取正在进行的根渲染 Lanes
export function getWorkInProgressRootRenderLanes(): Lanes {
return workInProgressRootRenderLanes;
}
4. 有待提交的效果
export function hasPendingCommitEffects(): boolean {
return (
pendingEffectsStatus !== NO_PENDING_EFFECTS &&
pendingEffectsStatus !== PENDING_PASSIVE_PHASE
);
}
5. 获取具有待处理被动效果的根
export function getRootWithPendingPassiveEffects(): FiberRoot | null {
return pendingEffectsStatus === PENDING_PASSIVE_PHASE
? pendingEffectsRoot
: null;
}
6. 获取待处理的被动效果 Lanes
export function getPendingPassiveEffectsLanes(): Lanes {
return pendingEffectsLanes;
}
7. 获取待处理过渡类型
export function getPendingTransitionTypes(): null | TransitionTypes {
return pendingTransitionTypes;
}
8. 获取当前时间
export function getCurrentTime(): number {
return now();
}
9. 获取执行上下文
export function getExecutionContext(): ExecutionContext {
return executionContext;
}
10. 获取纠缠渲染通道
export function getEntangledRenderLanes(): Lanes {
return entangledRenderLanes;
}
七、设置变量
1. 设置纠缠渲染通道
// This is called by the HiddenContext module when we enter or leave a
// hidden subtree. The stack logic is managed there because that's the only
// place that ever modifies it. Which module it lives in doesn't matter for
// performance because this function will get inlined regardless
//
// 当我们进入或离开隐藏子树时,会由 HiddenContext 模块调用此函数。堆栈逻辑在该模块中管理,因为
// 这是唯一会修改它的地方。它属于哪个模块对性能没有影响,因为无论如何这个函数都会被内联
export function setEntangledRenderLanes(newEntangledRenderLanes: Lanes) {
entangledRenderLanes = newEntangledRenderLanes;
}
2. 设置是否运行插入效果
export function setIsRunningInsertionEffect(isRunning: boolean): void {
if (__DEV__) {
isRunningInsertionEffect = isRunning;
}
}
八、请求更新 Lane
信息
disableLegacyMode在 shared 中ConcurrentMode在 ReactTypeOfMode 中pickArbitraryLane()由 ReactFiberLane#pickArbitraryLane 实现requestCurrentTransition()由 ReactFiberTransition#requestCurrentTransition 实现requestTransitionLane()由 ReactFiberRootScheduler#requestTransitionLane 实现eventPriorityToLane()由 ReactEventPriorities#eventPriorityToLane 实现resolveUpdatePriority()由宿主环境提供
export function requestUpdateLane(fiber: Fiber): Lane {
// Special cases
// 特殊情况
const mode = fiber.mode;
if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) {
return SyncLane as Lane;
} else if (
(executionContext & RenderContext) !== NoContext &&
workInProgressRootRenderLanes !== NoLanes
) {
// This is a render phase update. These are not officially supported. The
// old behavior is to give this the same "thread" (lanes) as
// whatever is currently rendering. So if you call `setState` on a component
// that happens later in the same render, it will flush. Ideally, we want to
// remove the special case and treat them as if they came from an
// interleaved event. Regardless, this pattern is not officially supported.
// This behavior is only a fallback. The flag only exists until we can roll
// out the setState warning, since existing code might accidentally rely on
// the current behavior.
//
// 这是一次渲染阶段的更新。这些不是官方支持的。
// 旧的行为是给它同样的“线程”(lane)作为
// 当前正在渲染的内容。因此,如果你在同一次渲染中稍后调用组件的 `setState`,
// 它会立即刷新。理想情况下,我们希望移除这个特殊情况,并把它们当作来自交错事件的更新处理。
// 不管怎样,这种模式不是官方支持的。
// 这种行为仅仅是一个回退。这个标志的存在只是为了在我们推出 setState 警告之前,
// 因为现有代码可能偶尔依赖于当前行为。
return pickArbitraryLane(workInProgressRootRenderLanes);
}
const transition = requestCurrentTransition();
if (transition !== null) {
if (enableGestureTransition) {
if (transition.gesture) {
throw new Error(
'Cannot setState on regular state inside a startGestureTransition. ' +
'Gestures can only update the useOptimistic() hook. There should be no ' +
'side-effects associated with starting a Gesture until its Action is ' +
'invoked. Move side-effects to the Action instead.',
);
}
}
if (__DEV__) {
if (!transition._updatedFibers) {
transition._updatedFibers = new Set();
}
transition._updatedFibers.add(fiber);
}
return requestTransitionLane(transition);
}
return eventPriorityToLane(resolveUpdatePriority());
}
九、请求延迟车道
信息
includesSomeLane()由 ReactFiberLane#includesSomeLane 实现getIsHydrating()由 ReactFiberHydrationContext#getIsHydrating 实现claimNextTransitionDeferredLane()由 ReactFiberLane#claimNextTransitionDeferredLane 实现getSuspenseHandler()由 ReactFiberSuspenseContext#getSuspenseHandler 实现
export function requestDeferredLane(): Lane {
if (workInProgressDeferredLane === NoLane) {
// If there are multiple useDeferredValue hooks in the same render, the
// tasks that they spawn should all be batched together, so they should all
// receive the same lane.
//
// 如果在同一次渲染中有多个 useDeferredValue 钩子,它们生成的任务应该全部批量
// 处理,因此它们都应该接收相同的通道。
// Check the priority of the current render to decide the priority of the
// deferred task.
//
// 检查当前渲染的优先级,以决定延迟任务的优先级。
// OffscreenLane is used for prerendering, but we also use OffscreenLane
// for incremental hydration. It's given the lowest priority because the
// initial HTML is the same as the final UI. But useDeferredValue during
// hydration is an exception — we need to upgrade the UI to the final
// value. So if we're currently hydrating, we treat it like a transition.
//
// OffscreenLane 用于预渲染,但我们也使用 OffscreenLane 进行增量 hydration。
// 它的优先级最低,因为初始 HTML 与最终 UI 相同。
// 但在 hydration 期间使用 useDeferredValue 是一个例外 —— 我们需要将 UI 升级到
// 最终值。所以如果我们当前正在进行 hydration,我们会将其视为一次过渡。
const isPrerendering =
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane) &&
!getIsHydrating();
if (isPrerendering) {
// There's only one OffscreenLane, so if it contains deferred work, we
// should just reschedule using the same lane.
//
// 只有一个离屏赛道,所以如果它包含延迟工作,
// 我们应该使用相同的赛道重新调度。
workInProgressDeferredLane = OffscreenLane;
} else {
// Everything else is spawned as a transition.
// 其他所有内容都是作为过渡生成的。
workInProgressDeferredLane = claimNextTransitionDeferredLane();
}
}
// Mark the parent Suspense boundary so it knows to spawn the deferred lane.
// 标记父级 Suspense 边界,以便它知道生成延迟通道。
const suspenseHandler = getSuspenseHandler();
if (suspenseHandler !== null) {
// TODO: As an optimization, we shouldn't entangle the lanes at the root; we
// can entangle them using the baseLanes of the Suspense boundary instead.
// We only need to do something special if there's no Suspense boundary.
//
// 待办:作为一种优化,我们不应该在根节点纠缠这些 lanes;
// 我们可以使用 Suspense 边界的 baseLanes 来纠缠它们。
// 只有在没有 Suspense 边界时,我们才需要做一些特别处理。
suspenseHandler.flags |= DidDefer;
}
return workInProgressDeferredLane;
}
十、计划视图切换事件
备注
createViewTransitionInstance()由宿主环境提供getViewTransitionName()由 ReactFiberViewTransitionComponent#getViewTransitionName 实现
export function scheduleViewTransitionEvent(
fiber: Fiber,
callback: ?((instance: ViewTransitionInstance, types: Array<string>) => void),
): void {
if (enableViewTransition) {
if (callback != null) {
const state: ViewTransitionState = fiber.stateNode;
let instance = state.ref;
if (instance === null) {
instance = state.ref = createViewTransitionInstance(
getViewTransitionName(fiber.memoizedProps, state),
);
}
if (pendingViewTransitionEvents === null) {
pendingViewTransitionEvents = [];
}
pendingViewTransitionEvents.push(callback.bind(null, instance));
}
}
}
十一、查看延迟 Lane
export function peekDeferredLane(): Lane {
return workInProgressDeferredLane;
}
十二、在 Fiber 上调度更新
备注
addFiberToLanesMap()由 ReactFiberLane#addFiberToLanesMap 实现mergeLanes()由 ReactFiberLane#mergeLanes 实现addTransitionToLanesMap()由 ReactFiberLane#addTransitionToLanesMap 实现ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现flushSyncWorkOnLegacyRootsOnly()由 ReactFiberRootScheduler#flushSyncWorkOnLegacyRootsOnly 实现
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
lane: Lane,
) {
if (__DEV__) {
if (isRunningInsertionEffect) {
console.error('useInsertionEffect must not schedule updates.');
}
}
if (__DEV__) {
if (isFlushingPassiveEffects) {
didScheduleUpdateDuringPassiveEffects = true;
}
}
// Check if the work loop is currently suspended and waiting for data to
// finish loading.
//
// 检查工作循环当前是否被挂起并等待数据加载完成。
if (
// Suspended render phase
// 挂起的渲染阶段
(root === workInProgressRoot &&
(workInProgressSuspendedReason === SuspendedOnData ||
workInProgressSuspendedReason === SuspendedOnAction)) ||
// Suspended commit phase
// 挂起的提交阶段
root.cancelPendingCommit !== null
) {
// The incoming update might unblock the current render. Interrupt the
// current attempt and restart from the top.
//
// 即将到来的更新可能会解除当前的渲染阻塞。
// 中断当前尝试,并从头重新开始。
prepareFreshStack(root, NoLanes);
const didAttemptEntireTree = false;
markRootSuspended(
root,
workInProgressRootRenderLanes,
workInProgressDeferredLane,
didAttemptEntireTree,
);
}
// Mark that the root has a pending update.
// 标记根节点有待处理的更新。
markRootUpdated(root, lane);
if (
(executionContext & RenderContext) !== NoContext &&
root === workInProgressRoot
) {
// This update was dispatched during the render phase. This is a mistake
// if the update originates from user space (with the exception of local
// hook updates, which are handled differently and don't reach this
// function), but there are some internal React features that use this as
// an implementation detail, like selective hydration.
//
// 这个更新是在渲染阶段分发的。如果更新来源于用户空间,这是一个错误(本地 hook 更新
// 除外,本地 hook 更新处理方式不同,并且不会达到此函数),但有一些 React 内部特性
// 将其作为实现细节使用,比如选择性水合。
warnAboutRenderPhaseUpdatesInDEV(fiber);
// Track lanes that were updated during the render phase
// 跟踪在渲染阶段更新的通道
workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
workInProgressRootRenderPhaseUpdatedLanes,
lane,
);
} else {
// This is a normal update, scheduled from outside the render phase. For
// example, during an input event.
//
// 这是一个普通的更新,从渲染阶段之外调度。例如,在输入事件期间。
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
addFiberToLanesMap(root, fiber, lane);
}
}
warnIfUpdatesNotWrappedWithActDEV(fiber);
if (enableTransitionTracing) {
const transition = ReactSharedInternals.T;
if (transition !== null && transition.name != null) {
if (transition.startTime === -1) {
transition.startTime = now();
}
addTransitionToLanesMap(root, transition, lane);
}
}
if (root === workInProgressRoot) {
// Received an update to a tree that's in the middle of rendering. Mark
// that there was an interleaved update work on this root.
//
// 收到了一个正在渲染中的树的更新。标记该根节点上有交错的更新工作。
if ((executionContext & RenderContext) === NoContext) {
workInProgressRootInterleavedUpdatedLanes = mergeLanes(
workInProgressRootInterleavedUpdatedLanes,
lane,
);
}
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// The root already suspended with a delay, which means this render
// definitely won't finish. Since we have a new update, let's mark it as
// suspended now, right before marking the incoming update. This has the
// effect of interrupting the current render and switching to the update.
// TODO: Make sure this doesn't override pings that happen while we've
// already started rendering.
//
// 根已经挂起并延迟,这意味着此次渲染
// 肯定无法完成。由于我们有一个新的更新,让我们现在将其标记为
// 挂起,就在标记传入更新之前。这会
// 打断当前渲染并切换到更新。
// TODO: 确保这不会覆盖在我们已经开始渲染时发生的 ping。
const didAttemptEntireTree = false;
markRootSuspended(
root,
workInProgressRootRenderLanes,
workInProgressDeferredLane,
didAttemptEntireTree,
);
}
}
ensureRootIsScheduled(root);
if (
lane === SyncLane &&
executionContext === NoContext &&
!disableLegacyMode &&
(fiber.mode & ConcurrentMode) === NoMode
) {
if (__DEV__ && ReactSharedInternals.isBatchingLegacy) {
// 即使在旧模式下,也要将 `act` 当作在 `batchedUpdates` 内部处理。
} else {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
//
// 现在刷新同步工作,除非我们已经在工作中或处于批处理中。
// 这故意放在 scheduleUpdateOnFiber 内,而不是
// scheduleCallbackForFiber 中,以保留在不立即刷新回调的情况下调度回调的能力。
// 我们只对用户发起的更新执行此操作,以保留旧模式的历史行为。
resetRenderTimer();
flushSyncWorkOnLegacyRootsOnly();
}
}
}
}
十三、在根组件上安排初始水合
备注
ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 提供
export function scheduleInitialHydrationOnRoot(root: FiberRoot, lane: Lane) {
// This is a special fork of scheduleUpdateOnFiber that is only used to
// schedule the initial hydration of a root that has just been created. Most
// of the stuff in scheduleUpdateOnFiber can be skipped.
//
// 这是 scheduleUpdateOnFiber 的一个特殊分支,它仅用于
// 安排刚创建的根节点的初始水合。大部分 scheduleUpdateOnFiber 中的内容都可以跳过。
//
// The main reason for this separate path, though, is to distinguish the
// initial children from subsequent updates. In fully client-rendered roots
// (createRoot instead of hydrateRoot), all top-level renders are modeled as
// updates, but hydration roots are special because the initial render must
// match what was rendered on the server.
//
// 不过,这条单独路径的主要原因是为了区分初始子节点与后续更新。在完全客户端渲染的根节点
// (使用 createRoot 而非 hydrateRoot)中,所有顶层渲染都被视为更新,但 hydration
// 根节点是特殊的,因为初始渲染必须与服务器上渲染的内容相匹配。
const current = root.current;
current.lanes = lane;
markRootUpdated(root, lane);
ensureRootIsScheduled(root);
}
十四、判定
1. 工作循环是否在数据上挂起
export function isWorkLoopSuspendedOnData(): boolean {
return (
workInProgressSuspendedReason === SuspendedOnData ||
workInProgressSuspendedReason === SuspendedOnAction
);
}
2. 是否为不安全的类渲染阶段更新
export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber): boolean {
// Check if this is a render phase update. Only called by class components,
// which special (deprecated) behavior for UNSAFE_componentWillReceive props.
//
// 检查这是否是渲染阶段的更新。仅由类组件调用,
// 这些组件对 UNSAFE_componentWillReceive props 有特殊(已废弃)的行为。
return (executionContext & RenderContext) !== NoContext;
}
3. 判断是否已在渲染
export function isAlreadyRendering(): boolean {
// Used by the renderer to print a warning if certain APIs are called from
// the wrong context, and for profiling warnings.
//
// 渲染器用于在某些 API 从错误的上下文调用时打印警告,并用于性能分析警告。
return (executionContext & (RenderContext | CommitContext)) !== NoContext;
}
4. 事件函数的执行上下文无效
export function isInvalidExecutionContextForEventFunction(): boolean {
// Used to throw if certain APIs are called from the wrong context.
// 如果从错误的上下文调用某些 API,则会抛出异常。
return (executionContext & RenderContext) !== NoContext;
}
5. 判定是否为已失败的旧版错误边界
export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
return (
legacyErrorBoundariesThatAlreadyFailed !== null &&
legacyErrorBoundariesThatAlreadyFailed.has(instance)
);
}
十五、在根节点上执行工作
备注
logSuspendedYieldTime()由 ReactFiberPerformanceTrack#logSuspendedYieldTime 提供logActionYieldTime()由 ReactFiberPerformanceTrack#logActionYieldTime 提供logYieldTime()由 ReactFiberPerformanceTrack#logYieldTime 提供includesBlockingLane()由 ReactFiberLane#includesBlockingLane 提供includesExpiredLane()由 ReactFiberLane#includesExpiredLane 提供checkIfRootIsPrerendering()由 ReactFiberLane#checkIfRootIsPrerendering 提供startYieldTimer()由 ReactProfilerTimer#startYieldTimer 提供now()由 Scheduler#now 提供setCurrentTrackFromLanes()由 [ReactFiberLane#setCurrentTrackFromLanes] 提供logInconsistentRender()由 ReactFiberPerformanceTrack#logInconsistentRender 提供getLanesToRetrySynchronouslyOnError()由 ReactFiberLane#getLanesToRetrySynchronouslyOnError 提供setCurrentTrackFromLanes()由 ReactFiberPerformanceTrack#setCurrentTrackFromLanes 提供logErroredRenderPhase()由 ReactFiberPerformanceTrack#logErroredRenderPhase 提供ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 提供
export function performWorkOnRoot(
root: FiberRoot,
lanes: Lanes,
forceSync: boolean,
): void {
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (workInProgressRootRenderLanes !== NoLanes && workInProgress !== null) {
const yieldedFiber = workInProgress;
// We've returned from yielding to the event loop. Let's log the time it took.
// 我们已经从让出事件循环返回。让我们记录所花费的时间。
const yieldEndTime = now();
switch (yieldReason) {
case SuspendedOnImmediate:
case SuspendedOnData:
logSuspendedYieldTime(yieldStartTime, yieldEndTime, yieldedFiber);
break;
case SuspendedOnAction:
logActionYieldTime(yieldStartTime, yieldEndTime, yieldedFiber);
break;
default:
logYieldTime(yieldStartTime, yieldEndTime);
}
}
}
// We disable time-slicing in some cases: if the work has been CPU-bound
// for too long ("expired" work, to prevent starvation), or we're in
// sync-updates-by-default mode.
//
// 我们在某些情况下会禁用时间切片:如果工作长时间受到 CPU 限制(“过期”工作,以
// 防止饥饿),或者我们处于默认同步更新模式。
const shouldTimeSlice =
(!forceSync &&
!includesBlockingLane(lanes) &&
!includesExpiredLane(root, lanes)) ||
// If we're prerendering, then we should use the concurrent work loop
// even if the lanes are synchronous, so that prerendering never blocks
// the main thread.
//
// 如果我们在进行预渲染,那么我们应该使用并发工作循环即使这些通道是同步的,这样预渲染
// 也不会阻塞主线程。
//
// TODO: We should consider doing this whenever a sync lane is suspended,
// even for regular pings.
// 待办事项:每当同步通道被挂起时,我们都应该考虑这样做,
// 即使是普通的 ping 也一样。
checkIfRootIsPrerendering(root, lanes);
let exitStatus: RootExitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes, true);
let renderWasConcurrent = shouldTimeSlice;
do {
if (exitStatus === RootInProgress) {
// Render phase is still in progress.
// 渲染阶段仍在进行中。
if (workInProgressRootIsPrerendering && !shouldTimeSlice) {
// We're in prerendering mode, but time slicing is not enabled. This
// happens when something suspends during a synchronous update. Exit the
// the work loop. When we resume, we'll use the concurrent work loop so
// that prerendering is non-blocking.
//
// 我们处于预渲染模式,但时间切片未启用。这个情况发生在同步更新期间有某些操作挂起时。
// 退出工作循环。当我们恢复时,我们将使用并发工作循环,以使预渲染非阻塞。
//
// Mark the root as suspended. Usually we do this at the end of the
// render phase, but we do it here so that we resume in
// prerendering mode.
// TODO: Consider always calling markRootSuspended immediately.
// Needs to be *after* we attach a ping listener, though.
//
// 标记根节点为挂起状态。通常我们在渲染阶段结束时进行此操作,但我们在这里进行,以
// 便在预渲染模式下恢复。
// TODO: 考虑始终立即调用 markRootSuspended。不过需要在我们附加 ping 监听器之后进行。
const didAttemptEntireTree = false;
markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
}
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// We're about to yield. Let's keep track of how long we yield to the event loop.
// We also stash the suspended reason at the time we yielded since it might have
// changed when we resume such as when it gets pinged.
//
// 我们即将让出控制权。让我们跟踪向事件循环让出的时间。
// 我们还会在让出时保存暂停的原因,因为在恢复时它可能已经改变,例如当它被 ping 时。
startYieldTimer(workInProgressSuspendedReason);
}
break;
} else {
let renderEndTime = 0;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
renderEndTime = now();
}
// The render completed.
// 渲染完成。
// Check if this render may have yielded to a concurrent event, and if so,
// confirm that any newly rendered stores are consistent.
// TODO: It's possible that even a concurrent render may never have yielded
// to the main thread, if it was fast enough, or if it expired. We could
// skip the consistency check in that case, too.
//
// 检查此渲染是否可能已让位于并发事件,如果是的话,确认任何新渲染的 store 是否一致。
// 待办事项:即使是并发渲染,也有可能从未让位于主线程,如果渲染足够快,或者已过期。在
// 这种情况下,我们也可以跳过一致性检查。
const finishedWork: Fiber = root.current.alternate as any;
if (
renderWasConcurrent &&
!isRenderConsistentWithExternalStores(finishedWork)
) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logInconsistentRender(
renderStartTime,
renderEndTime,
workInProgressUpdateTask,
);
finalizeRender(lanes, renderEndTime);
}
// A store was mutated in an interleaved event. Render again,
// synchronously, to block further mutations.
//
// 一个存储在交错事件中被改变了。请再次渲染,同步进行,以阻止进一步的更改。
exitStatus = renderRootSync(root, lanes, false);
// We assume the tree is now consistent because we didn't yield to any
// concurrent events.
//
// 我们假设树现在是一致的,因为我们没有让步于任何并发事件。
renderWasConcurrent = false;
// Need to check the exit status again.
// 需要再次检查退出状态。
continue;
}
// Check if something threw
// 检查是否有异常抛出
if (
(disableLegacyMode || root.tag !== LegacyRoot) &&
exitStatus === RootErrored
) {
const lanesThatJustErrored = lanes;
const errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
lanesThatJustErrored,
);
if (errorRetryLanes !== NoLanes) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logErroredRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
finalizeRender(lanes, renderEndTime);
}
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
lanesThatJustErrored,
errorRetryLanes,
);
renderWasConcurrent = false;
// Need to check the exit status again.
// 需要再次检查退出状态。
if (exitStatus !== RootErrored) {
// The root did not error this time. Restart the exit algorithm
// from the beginning.
//
// 这次根没有出错。重新从头开始退出算法。
//
// TODO: Refactor the exit algorithm to be less confusing. Maybe
// more branches + recursion instead of a loop. I think the only
// thing that causes it to be a loop is the RootSuspendedAtTheShell
// check. If that's true, then we don't need a loop/recursion
// at all.
//
// 待办事项:重构退出算法,使其不那么混乱。也许可以多用分支和递归而不是循环。我
// 认为唯一让它变成循环的原因是 RootSuspendedAtTheShell 的检查。如果为真,
// 那么我们就不需要循环或递归所有
continue;
} else {
// The root errored yet again. Proceed to commit the tree.
//
// 根目录再次出错。继续提交树。
if (enableProfilerTimer && enableComponentPerformanceTrack) {
renderEndTime = now();
}
}
}
}
if (exitStatus === RootFatalErrored) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logErroredRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
finalizeRender(lanes, renderEndTime);
}
prepareFreshStack(root, NoLanes);
// Since this is a fatal error, we're going to pretend we attempted
// the entire tree, to avoid scheduling a prerender.
//
// 由于这是一个致命错误,我们将假装已经尝试了整个树,以避免安排预渲染。
const didAttemptEntireTree = true;
markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
break;
}
// 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.
//
// 我们现在有了一颗一致的树。下一步是提交它,
// 或者,如果有任务被挂起,则在超时后再提交它。
finishConcurrentRender(
root,
exitStatus,
finishedWork,
lanes,
renderEndTime,
);
}
break;
} while (true);
ensureRootIsScheduled(root);
}
十六、队列可恢复错误
export function queueRecoverableErrors(errors: Array<CapturedValue<mixed>>) {
if (workInProgressRootRecoverableErrors === null) {
workInProgressRootRecoverableErrors = errors;
} else {
workInProgressRootRecoverableErrors.push.apply(
workInProgressRootRecoverableErrors,
errors,
);
}
}
十七、刷新
1. 刷新根节点
备注
upgradePendingLanesToSync()由 ReactFiberLane#upgradePendingLanesToSync 提供ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 提供flushSyncWorkOnAllRoots()由 ReactFiberRootScheduler#flushSyncWorkOnAllRoots 提供
export function flushRoot(root: FiberRoot, lanes: Lanes) {
if (lanes !== NoLanes) {
upgradePendingLanesToSync(root, lanes);
ensureRootIsScheduled(root);
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
resetRenderTimer();
// TODO: For historical reasons this flushes all sync work across all
// roots. It shouldn't really matter either way, but we could change this
// to only flush the given root.
//
// 待办事项:出于历史原因,这会刷新所有根节点上的所有同步工作。
// 无论如何,这实际上没有太大影响,但我们可以将其改为仅刷新指定的根节点。
flushSyncWorkOnAllRoots();
}
}
}
2. 来自协调器的同步刷新
备注
getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供flushSyncWorkOnAllRoots()由 ReactFiberRootScheduler#flushSyncWorkOnAllRoots 提供
export function flushSyncFromReconciler<R>(fn: (() => R) | void): R | void {
// In legacy mode, we flush pending passive effects at the beginning of the
// next event, not at the end of the previous one.
//
// 在旧模式下,我们会在下一个事件开始时刷新待处理的被动效果,而不是在上一个事件结束时刷新。
if (
pendingEffectsStatus !== NO_PENDING_EFFECTS &&
!disableLegacyMode &&
pendingEffectsRoot.tag === LegacyRoot &&
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
flushPendingEffects();
}
const prevExecutionContext = executionContext;
executionContext |= BatchedContext;
const prevTransition = ReactSharedInternals.T;
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DiscreteEventPriority);
ReactSharedInternals.T = null;
if (fn) {
return fn();
} else {
return undefined;
}
} finally {
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
executionContext = prevExecutionContext;
// Flush the immediate callbacks that were scheduled during this batch.
// Note that this will happen even if batchedUpdates is higher up
// the stack.
//
// 刷新在此批处理中安排的即时回调。
// 请注意,即使 batchedUpdates 在调用栈的更高层,这也会发生。
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
flushSyncWorkOnAllRoots();
}
}
}
3. 同步刷新工作
备注
flushSyncWorkOnAllRoots()由 ReactFiberRootScheduler#flushSyncWorkOnAllRoots 实现
// If called outside of a render or commit will flush all sync work on all roots
// Returns whether the the call was during a render or not
//
// 如果在渲染或提交之外调用,将刷新所有根上的所有同步工作。返回调用是否发生在渲染期间
export function flushSyncWork(): boolean {
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
flushSyncWorkOnAllRoots();
return false;
}
return true;
}
4. 延迟刷新待处理效果
export function flushPendingEffectsDelayed(): boolean {
if (pendingDelayedCommitReason === IMMEDIATE_COMMIT) {
pendingDelayedCommitReason = DELAYED_PASSIVE_COMMIT;
}
return flushPendingEffects();
}
5. 刷新待处理效果
备注
stopViewTransition()由宿主环境提供
export function flushPendingEffects(): boolean {
// Returns whether passive effects were flushed.
// 返回被动效果是否已刷新。
if (enableViewTransition && pendingViewTransition !== null) {
// If we forced a flush before the View Transition full started then we skip it.
// This ensures that we're not running a partial animation.
//
// 如果我们在视图过渡完全开始之前强制刷新,那么我们就会跳过它。这确保了我们不会运行部分动画。
stopViewTransition(pendingViewTransition);
if (__DEV__) {
if (!didWarnAboutInterruptedViewTransitions) {
didWarnAboutInterruptedViewTransitions = true;
console.warn(
'A flushSync update cancelled a View Transition because it was called ' +
'while the View Transition was still preparing. To preserve the synchronous ' +
'semantics, React had to skip the View Transition. If you can, try to avoid ' +
"flushSync() in a scenario that's likely to interfere.",
);
}
}
pendingViewTransition = null;
pendingDelayedCommitReason = ABORTED_VIEW_TRANSITION_COMMIT;
}
flushGestureMutations();
flushGestureAnimations();
flushMutationEffects();
flushLayoutEffects();
// Skip flushAfterMutation if we're forcing this early.
// 如果我们强制提前执行,则跳过 flushAfterMutation。
flushSpawnedWork();
return flushPassiveEffects();
}
十八、更新
1. 延迟更新
备注
getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供
export function deferredUpdates<A>(fn: () => A): A {
const prevTransition = ReactSharedInternals.T;
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DefaultEventPriority);
ReactSharedInternals.T = null;
return fn();
} finally {
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}
2. 批量更新
备注
flushSyncWorkOnLegacyRootsOnly()由 ReactFiberRootScheduler#flushSyncWorkOnLegacyRootsOnly 提供
export function batchedUpdates<A, R>(fn: A => R, a: A): R {
if (disableLegacyMode) {
// batchedUpdates is a no-op now, but there's still some internal react-dom
// code calling it, that we can't remove until we remove legacy mode.
//
// batchedUpdates 现在是一个空操作,但仍然有一些内部的 react-dom 代码在调用它,直到
// 我们移除旧版模式之前,我们无法删除这些调用。
return fn(a);
} else {
const prevExecutionContext = executionContext;
executionContext |= BatchedContext;
try {
return fn(a);
} finally {
executionContext = prevExecutionContext;
// If there were legacy sync updates, flush them at the end of the outer
// most batchedUpdates-like method.
//
// 如果有遗留的同步更新,在最外层类似 batchedUpdates 的方法结束时将它们刷新。
if (
executionContext === NoContext &&
// Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
// 即使在旧模式下,也要将 `act` 当作在 `batchedUpdates` 内部处理。
!(__DEV__ && ReactSharedInternals.isBatchingLegacy)
) {
resetRenderTimer();
flushSyncWorkOnLegacyRootsOnly();
}
}
}
}
3. 离散更新
备注
getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供
export function discreteUpdates<A, B, C, D, R>(
fn: (A, B, C, D) => R,
a: A,
b: B,
c: C,
d: D,
): R {
const prevTransition = ReactSharedInternals.T;
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(DiscreteEventPriority);
ReactSharedInternals.T = null;
return fn(a, b, c, d);
} finally {
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
if (executionContext === NoContext) {
resetRenderTimer();
}
}
}
十九、应保留在前一个屏幕
备注
getSuspenseHandler()由 ReactFiberSuspenseContext#getSuspenseHandler 提供includesOnlyTransitions()由 ReactFiberLane#includesOnlyTransitions 提供getShellBoundary()由 ReactFiberSuspenseContext#getShellBoundary 提供includesOnlyRetries()由 ReactFiberLane#includesOnlyRetries 提供includesSomeLane()由 ReactFiberLane#includesSomeLane 提供
export function shouldRemainOnPreviousScreen(): boolean {
// This is asking whether it's better to suspend the transition and remain
// on the previous screen, versus showing a fallback as soon as possible. It
// takes into account both the priority of render and also whether showing a
// fallback would produce a desirable user experience.
//
// 这是在询问,是暂停过渡并停留在前一个屏幕更好,还是尽快显示备用内容更好。
// 它同时考虑了渲染的优先级以及显示备用内容是否会带来理想的用户体验。
const handler = getSuspenseHandler();
if (handler === null) {
// There's no Suspense boundary that can provide a fallback. We have no
// choice but to remain on the previous screen.
// NOTE: We do this even for sync updates, for lack of any better option. In
// the future, we may change how we handle this, like by putting the whole
// root into a "detached" mode.
//
// 没有任何 Suspense 边界可以提供备用界面。我们别无选择,只能停留在之前的屏幕。
// 注意:即使是同步更新,我们也会这样做,因为没有更好的选择。
// 将来,我们可能会改变处理方式,例如将整个根节点置于“分离”模式。
return true;
}
// TODO: Once `use` has fully replaced the `throw promise` pattern, we should
// be able to remove the equivalent check in finishConcurrentRender, and rely
// just on this one.
//
// 待办事项:一旦 `use` 完全取代了 `throw promise` 模式,我们应该能够移除
// finishConcurrentRender 中的等效检查,只依赖这一处。
if (includesOnlyTransitions(workInProgressRootRenderLanes)) {
if (getShellBoundary() === null) {
// We're rendering inside the "shell" of the app. Activating the nearest
// fallback would cause visible content to disappear. It's better to
// suspend the transition and remain on the previous screen.
// 我们正在应用程序的“壳”内进行渲染。激活最近的备用方案会导致可见内容消失。最好是
// 暂停过渡,保持在前一个屏幕上。
return true;
} else {
// We're rendering content that wasn't part of the previous screen.
// Rather than block the transition, it's better to show a fallback as
// soon as possible. The appearance of any nested fallbacks will be
// throttled to avoid jank.
//
// 我们正在渲染之前屏幕中不存在的内容。与其阻塞过渡,不如尽快显示一个备用内容。
// 任何嵌套备用内容的出现都会被限制以避免卡顿。
return false;
}
}
if (
includesOnlyRetries(workInProgressRootRenderLanes) ||
// In this context, an OffscreenLane counts as a Retry
// TODO: It's become increasingly clear that Retries and Offscreen are
// deeply connected. They probably can be unified further.
//
// 在这种情况下,OffscreenLane 算作一次重试
// 待办:越来越明显的是,重试和 Offscreen 密切相关。它们可能可以进一步统一。
includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// During a retry, we can suspend rendering if the nearest Suspense boundary
// is the boundary of the "shell", because we're guaranteed not to block
// any new content from appearing.
//
// 在重试期间,如果最近的 Suspense 边界是“shell”的边界,我们可以暂停渲染,因为可以保证
// 不会阻塞任何新内容的显示。
//
// The reason we must check if this is a retry is because it guarantees
// that suspending the work loop won't block an actual update, because
// retries don't "update" anything; they fill in fallbacks that were left
// behind by a previous transition.
//
// 我们必须检查这是否是重试的原因是,它能保证暂停工作循环不会阻塞实际的更新,因为重试不
// 会“更新”任何东西;它们会填补之前转换中留下的备用内容。
return handler === getShellBoundary();
}
// For all other Lanes besides Transitions and Retries, we should not wait
// for the data to load.
// 对于除了过渡和重试之外的所有其他通道,我们不应该等待数据加载。
return false;
}
二十、标记状态
1. 标记渲染派生原因
export function markRenderDerivedCause(fiber: Fiber): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (__DEV__) {
if (workInProgressUpdateTask === null) {
// If we don't have a cause associated with this render, it's likely because some
// other render left work behind on this Fiber. The real cause is this Fiber itself.
// We use its debugTask as the cause for this render. This might not be the only
// one when multiple siblings are rendered but they ideally shouldn't be.
//
// 如果我们没有与此渲染关联的原因,很可能是因为其他渲染在这个 Fiber 上留下了工作。真正的原因是
// 这个 Fiber 本身。我们使用它的 debugTask 作为此次渲染的原因。当多个兄弟节点被渲染时,这可能
// 不是唯一的原因,但理想情况下不应出现这种情况。
workInProgressUpdateTask =
fiber._debugTask == null ? null : fiber._debugTask;
}
}
}
}
2. 标记过渡已开始
备注
now()由 Scheduler#now 提供
export function markTransitionStarted() {
globalMostRecentTransitionTime = now();
}
3. 标记回退的提交时间
备注
now()由 Scheduler#now 提供
export function markCommitTimeOfFallback() {
globalMostRecentFallbackTime = now();
}
4. 标记已跳过的更新通道
备注
mergeLanes()由 ReactFiberLane#mergeLanes 提供
export function markSkippedUpdateLanes(lane: Lane | Lanes): void {
workInProgressRootSkippedLanes = mergeLanes(
lane,
workInProgressRootSkippedLanes,
);
}
5. 标记已生成的重试通道
备注
mergeLanes()由 ReactFiberLane#mergeLanes 实现
export function markSpawnedRetryLane(lane: Lane): void {
// Keep track of the retry lanes that were spawned by a fallback during the
// current render and were not later pinged. This will represent the lanes
// that are known to still be suspended.
//
// 跟踪在当前渲染过程中由回退机制生成但随后未被 ping 的重试通道。
// 这将表示那些已知仍处于挂起状态的通道。
workInProgressSuspendedRetryLanes = mergeLanes(
workInProgressSuspendedRetryLanes,
lane,
);
}
6. 将旧版错误边界标记为失败
export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
if (legacyErrorBoundariesThatAlreadyFailed === null) {
legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
} else {
legacyErrorBoundariesThatAlreadyFailed.add(instance);
}
}
廿一、渲染
1. 渲染已暂停
export function renderDidSuspend(): void {
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootSuspended;
}
}
2. 如果可能,延迟渲染挂起
备注
includesOnlyTransitions()由 ReactFiberLane#includesOnlyTransitions 提供getSuspenseHandler()由 ReactFiberSuspenseContext#getSuspenseHandler 提供includesNonIdleWork()由 ReactFiberLane#includesNonIdleWork 提供
export function renderDidSuspendDelayIfPossible(): void {
workInProgressRootExitStatus = RootSuspendedWithDelay;
if (
!workInProgressRootDidSkipSuspendedSiblings &&
// Check if the root will be blocked from committing.
// TODO: Consider aligning this better with the rest of the logic. Maybe
// we should only set the exit status to RootSuspendedWithDelay if this
// condition is true? And remove the equivalent checks elsewhere.
//
// 检查根是否会被阻止提交。
// TODO:考虑将此与其他逻辑更好地对齐。也许我们只有在此条件为真时才应将退出状态
// 设置为 RootSuspendedWithDelay?并删除其他地方的等效检查。
(includesOnlyTransitions(workInProgressRootRenderLanes) ||
getSuspenseHandler() === null)
) {
// This render may not have originally been scheduled as a prerender, but
// something suspended inside the visible part of the tree, which means we
// won't be able to commit a fallback anyway. Let's proceed as if this were
// a prerender so that we can warm up the siblings without scheduling a
// separate pass.
//
// 这个渲染可能最初并没有计划作为预渲染,但树的可见部分内部有某些内容被挂起,这意味着我们
// 无法提交回退。让我们继续将其视为预渲染,这样我们就可以在不安排单独过程的情况下预热兄弟节点。
workInProgressRootIsPrerendering = true;
}
// Check if there are updates that we skipped tree that might have unblocked
// this render.
// 检查是否有我们跳过的更新可能已经解除阻塞这个渲染。
if (
(includesNonIdleWork(workInProgressRootSkippedLanes) ||
includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)) &&
workInProgressRoot !== null
) {
// Mark the current render as suspended so that we switch to working on
// the updates that were skipped. Usually we only suspend at the end of
// the render phase.
// TODO: We should probably always mark the root as suspended immediately
// (inside this function), since by suspending at the end of the render
// phase introduces a potential mistake where we suspend lanes that were
// pinged or updated while we were rendering.
// TODO: Consider unwinding immediately, using the
// SuspendedOnHydration mechanism.
//
// 将当前渲染标记为挂起,以便我们切换去处理之前被跳过的更新。通常我们只会在渲染阶段结束时挂起。
// TODO: 我们可能应该总是在此函数内部立即将根节点标记为挂起,因为在渲染阶段结束时挂起可能会
// 导致我们挂起在渲染中被 ping 或更新的通道,从而引入潜在错误。
// TODO: 考虑立即回滚,使用 SuspendedOnHydration 机制。
const didAttemptEntireTree = false;
markRootSuspended(
workInProgressRoot,
workInProgressRootRenderLanes,
workInProgressDeferredLane,
didAttemptEntireTree,
);
}
}
3. 渲染出错
export function renderDidError() {
if (workInProgressRootExitStatus !== RootSuspendedWithDelay) {
workInProgressRootExitStatus = RootErrored;
}
}
4. 渲染尚未中断
// Called during render to determine if anything has suspended.
// Returns false if we're not sure.
//
// 在渲染期间调用以确定是否有任何内容被挂起。如果不确定,则返回 false。
export function renderHasNotSuspendedYet(): boolean {
// If something errored or completed, we can't really be sure,
// so those are false.
//
// 如果出现错误或已完成,我们其实无法确定,所以这些都是假的。
return workInProgressRootExitStatus === RootInProgress;
}
廿二、队列并发错误
export function queueConcurrentError(error: CapturedValue<mixed>) {
if (workInProgressRootConcurrentErrors === null) {
workInProgressRootConcurrentErrors = [error];
} else {
workInProgressRootConcurrentErrors.push(error);
}
}
廿三、捕获提交阶段错误
备注
recordEffectError()由 ReactProfilerTimer#recordEffectError 提供createCapturedValueAtFiber()由 ReactCapturedValue#createCapturedValueAtFiber 提供recordEffectError()由 ReactProfilerTimer#recordEffectError 提供createClassErrorUpdate()由 ReactFiberThrow#createClassErrorUpdate 提供initializeClassErrorUpdate()由 ReactFiberThrow#initializeClassErrorUpdate 提供ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 提供
export function captureCommitPhaseError(
sourceFiber: Fiber,
nearestMountedAncestor: Fiber | null,
error: mixed,
) {
if (__DEV__) {
setIsRunningInsertionEffect(false);
}
if (sourceFiber.tag === HostRoot) {
// Error was thrown at the root. There is no parent, so the root
// itself should capture it.
// 错误在根节点抛出。没有父节点,所以根节点本身应该捕获它。
captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
return;
}
let fiber = nearestMountedAncestor;
while (fiber !== null) {
if (fiber.tag === HostRoot) {
captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
return;
} else if (fiber.tag === ClassComponent) {
const ctor = fiber.type;
const instance = fiber.stateNode;
if (
typeof ctor.getDerivedStateFromError === 'function' ||
(typeof instance.componentDidCatch === 'function' &&
!isAlreadyFailedLegacyErrorBoundary(instance))
) {
const errorInfo = createCapturedValueAtFiber(error, sourceFiber);
if (enableProfilerTimer && enableComponentPerformanceTrack) {
recordEffectError(errorInfo);
}
const update = createClassErrorUpdate(SyncLane as Lane);
const root = enqueueUpdate(fiber, update, SyncLane as Lane);
if (root !== null) {
initializeClassErrorUpdate(update, root, fiber, errorInfo);
markRootUpdated(root, SyncLane);
ensureRootIsScheduled(root);
}
return;
}
}
fiber = fiber.return;
}
if (__DEV__) {
console.error(
'Internal React error: Attempted to capture a commit phase error ' +
'inside a detached tree. This indicates a bug in React. Potential ' +
'causes include deleting the same fiber more than once, committing an ' +
'already-finished tree, or an inconsistent return pointer.\n\n' +
'Error message:\n\n%s',
error,
);
}
}
廿四、 附加 Ping 监听器
export function attachPingListener(
root: FiberRoot,
wakeable: Wakeable,
lanes: Lanes,
) {
// Attach a ping listener
// 附加一个 ping 监听器
//
// The data might resolve before we have a chance to commit the fallback. Or,
// in the case of a refresh, we'll never commit a fallback. So we need to
// attach a listener now. When it resolves ("pings"), we can decide whether to
// try rendering the tree again.
//
// 数据可能在我们有机会提交备用方案之前就已经解决了。或者,在刷新情况下,我们可能根本不会提交
// 备用方案。因此我们需要现在就附加一个监听器。当它解决(“ping”)时,我们可以决定是否再次尝试
// 渲染树。
//
// Only attach a listener if one does not already exist for the lanes
// we're currently rendering (which acts like a "thread ID" here).
// 仅在当前渲染的通道尚未存在监听器时才附加监听器(这里相当于“线程 ID”)。
//
// We only need to do this in concurrent mode. Legacy Suspense always
// commits fallbacks synchronously, so there are no pings.
// 我们只需要在并发模式下执行此操作。传统的 Suspense 总是同步提交备用内容,因此不会有 ping。
let pingCache = root.pingCache;
let threadIDs;
if (pingCache === null) {
pingCache = root.pingCache = new PossiblyWeakMap();
threadIDs = new Set<mixed>();
pingCache.set(wakeable, threadIDs);
} else {
threadIDs = pingCache.get(wakeable);
if (threadIDs === undefined) {
threadIDs = new Set();
pingCache.set(wakeable, threadIDs);
}
}
if (!threadIDs.has(lanes)) {
workInProgressRootDidAttachPingListener = true;
// Memoize using the thread ID to prevent redundant listeners.
// 使用线程 ID 进行记忆化,以防止重复监听器。
threadIDs.add(lanes);
const ping = pingSuspendedRoot.bind(null, root, wakeable, lanes);
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
// If we have pending work still, restore the original updaters
// 如果我们还有未完成的工作,恢复原始的更新器
restorePendingUpdaters(root, lanes);
}
}
wakeable.then(ping, ping);
}
}
廿五、重试脱水悬挂边界
export function retryDehydratedSuspenseBoundary(boundaryFiber: Fiber) {
const suspenseState: null | SuspenseState = boundaryFiber.memoizedState;
let retryLane: Lane = NoLane;
if (suspenseState !== null) {
retryLane = suspenseState.retryLane;
}
retryTimedOutBoundary(boundaryFiber, retryLane);
}
廿六、解决可重试唤醒
export function resolveRetryWakeable(boundaryFiber: Fiber, wakeable: Wakeable) {
let retryLane: Lane = NoLane; // Default
let retryCache: WeakSet<Wakeable> | Set<Wakeable> | null;
switch (boundaryFiber.tag) {
case ActivityComponent:
case SuspenseComponent:
retryCache = boundaryFiber.stateNode;
const suspenseState: null | SuspenseState | ActivityState =
boundaryFiber.memoizedState;
if (suspenseState !== null) {
retryLane = suspenseState.retryLane;
}
break;
case SuspenseListComponent:
retryCache = boundaryFiber.stateNode;
break;
case OffscreenComponent: {
const instance: OffscreenInstance = boundaryFiber.stateNode;
retryCache = instance._retryCache;
break;
}
default:
throw new Error(
'Pinged unknown suspense boundary type. ' +
'This is probably a bug in React.',
);
}
if (retryCache !== null) {
// The wakeable resolved, so we no longer need to memoize, because it will
// never be thrown again.
// wakeable 已解决,所以我们不再需要记忆化,因为它不会再被抛出。
retryCache.delete(wakeable);
}
retryTimedOutBoundary(boundaryFiber, retryLane);
}
廿七、检测到无限更新循环时抛出
备注
mergeLanes()由 ReactFiberLane#mergeLanes 提供
export function throwIfInfiniteUpdateLoopDetected() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
nestedPassiveUpdateCount = 0;
rootWithNestedUpdates = null;
rootWithPassiveNestedUpdates = null;
if (enableInfiniteRenderLoopDetection) {
if (executionContext & RenderContext && workInProgressRoot !== null) {
// We're in the render phase. Disable the concurrent error recovery
// mechanism to ensure that the error we're about to throw gets handled.
// We need it to trigger the nearest error boundary so that the infinite
// update loop is broken.
//
// 我们正在渲染阶段。禁用并发错误恢复机制,以确保我们即将抛出的错误能够被处理。
// 我们需要它触发最近的错误边界,以打破无限更新循环。
workInProgressRoot.errorRecoveryDisabledLanes = mergeLanes(
workInProgressRoot.errorRecoveryDisabledLanes,
workInProgressRootRenderLanes,
);
}
}
throw new Error(
'Maximum update depth exceeded. This can happen when a component ' +
'repeatedly calls setState inside componentWillUpdate or ' +
'componentDidUpdate. React limits the number of nested updates to ' +
'prevent infinite loops.',
);
}
if (__DEV__) {
if (nestedPassiveUpdateCount > NESTED_PASSIVE_UPDATE_LIMIT) {
nestedPassiveUpdateCount = 0;
rootWithPassiveNestedUpdates = null;
console.error(
'Maximum update depth exceeded. This can happen when a component ' +
"calls setState inside useEffect, but useEffect either doesn't " +
'have a dependency array, or one of the dependencies changes on ' +
'every render.',
);
}
}
}
廿八、在未挂载的 Fiber 上发出更新警告(开发环境)
备注
getComponentNameFromFiber()由宿主环境提供runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现
export function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber: Fiber) {
if (__DEV__) {
if ((executionContext & RenderContext) !== NoContext) {
// We let the other warning about render phase updates deal with this one.
// 我们让另一个关于渲染阶段更新的警告来处理这个问题。
return;
}
if (!disableLegacyMode && !(fiber.mode & ConcurrentMode)) {
return;
}
const tag = fiber.tag;
if (
tag !== HostRoot &&
tag !== ClassComponent &&
tag !== FunctionComponent &&
tag !== ForwardRef &&
tag !== MemoComponent &&
tag !== SimpleMemoComponent
) {
// Only warn for user-defined components, not internal ones like Suspense.
// 仅对用户定义的组件发出警告,不包括像 Suspense 这样的内部组件。
return;
}
// We show the whole stack but dedupe on the top component's name because
// the problematic code almost always lies inside that component.
// 我们展示整个堆栈,但只根据最顶层组件的名称去重,因为问题代码几乎总是在该组件内。
const componentName = getComponentNameFromFiber(fiber) || 'ReactComponent';
if (didWarnStateUpdateForNotYetMountedComponent !== null) {
if (didWarnStateUpdateForNotYetMountedComponent.has(componentName)) {
return;
}
didWarnStateUpdateForNotYetMountedComponent.add(componentName);
} else {
didWarnStateUpdateForNotYetMountedComponent = new Set([componentName]);
}
runWithFiberInDEV(fiber, () => {
console.error(
"Can't perform a React state update on a component that hasn't mounted yet. " +
'This indicates that you have a side-effect in your render function that ' +
'asynchronously tries to update the component. Move this work to ' +
'useEffect instead.',
);
});
}
}
廿九、恢复待处理更新程序
备注
addFiberToLanesMap()由 ReactFiberLane#addFiberToLanesMap 提供
export function restorePendingUpdaters(root: FiberRoot, lanes: Lanes): void {
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
const memoizedUpdaters = root.memoizedUpdaters;
memoizedUpdaters.forEach(schedulingFiber => {
addFiberToLanesMap(root, schedulingFiber, lanes);
});
// This function intentionally does not clear memoized updaters.
// Those may still be relevant to the current commit
// and a future one (e.g. Suspense).
// 这个函数故意不清除已缓存的更新器。 它们可能仍与当前提交相关以及未来的提交(例如 Suspense)。
}
}
}
三十、常数
1. WeakMap 可用
备注
原文中 404 行
const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
2. 批处理上下文
备注
原文 409 行
const BatchedContext = 0b001;
3. 根
备注
源码中 413 - 420 行
注意
奇怪,不想 React 的风格,不应当创建一个名为 「ReactFiberRootFlags」 文件
// 正在获取根权限
const RootInProgress = 0;
// 根致命错误
const RootFatalErrored = 1;
// 根错误
const RootErrored = 2;
// 根挂起
const RootSuspended = 3;
// 延迟挂起根
const RootSuspendedWithDelay = 4;
// 根部在壳上挂起
const RootSuspendedAtTheShell = 6;
// 根目录完成
const RootCompleted = 5;
4. 暂停
备注
原文中 431 - 441 行
export type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
// 未暂停
const NotSuspended: SuspendedReason = 0;
// 因错误而暂停
const SuspendedOnError: SuspendedReason = 1;
// 挂起而等待数据
const SuspendedOnData: SuspendedReason = 2;
// 因 Immediate 而暂停
const SuspendedOnImmediate: SuspendedReason = 3;
// 因实例而暂停
const SuspendedOnInstance: SuspendedReason = 4;
// 因实例而暂停,准备继续
const SuspendedOnInstanceAndReadyToContinue: SuspendedReason = 5;
// 因弃用的 throw Promise 而挂起
const SuspendedOnDeprecatedThrowPromise: SuspendedReason = 6;
// 暂停后重试并继续
const SuspendedAndReadyToContinue: SuspendedReason = 7;
// 挂起以等待水合
const SuspendedOnHydration: SuspendedReason = 8;
// 挂起等待处理事件
const SuspendedOnAction: SuspendedReason = 9;
5. 提交状态/类型
备注
源码中的 702 - 708 hang
type SuspendedCommitReason = null | string;
type DelayedCommitReason = 0 | 1 | 2 | 3;
// 立即提交
const IMMEDIATE_COMMIT = 0;
// 已中止视图过渡提交
const ABORTED_VIEW_TRANSITION_COMMIT = 1;
// 延迟被动提交
const DELAYED_PASSIVE_COMMIT = 2;
// 动画已开始提交
const ANIMATION_STARTED_COMMIT = 3;
6. 待处理
备注
源码中的 710 - 717
const NO_PENDING_EFFECTS = 0; // 无待处理效果
const PENDING_MUTATION_PHASE = 1; // 待定变异阶段
const PENDING_LAYOUT_PHASE = 2; // 待布局阶段
const PENDING_AFTER_MUTATION_PHASE = 3; // 变异后待处理
const PENDING_SPAWNED_WORK = 4; // 待生成的工作
const PENDING_PASSIVE_PHASE = 5; // 待被动阶段
const PENDING_GESTURE_MUTATION_PHASE = 6; // 待处理手势变更阶段
const PENDING_GESTURE_ANIMATION_PHASE = 7; // 待处理手势动画阶段
7. 嵌套更新限制
// Use these to prevent an infinite loop of nested updates
// 使用这些来防止嵌套更新的无限循环
const NESTED_UPDATE_LIMIT = 50;
8. 嵌套被动更新限制
备注
源码中的 742 行
const NESTED_PASSIVE_UPDATE_LIMIT = 50;
9. 假动作回调节点
备注
在源码中 5236 行
const fakeActCallbackNode = {};
卅一、变量
1. 备用 Fiber
备注
原文中 422 - 429 行
// Describes where we are in the React execution stack
// 描述我们在 React 执行栈中的位置
let executionContext: ExecutionContext = NoContext;
// The root we're working on
// 正在处理的根
let workInProgressRoot: FiberRoot | null = null;
// The fiber we're working on
// 正在处理的 Fiber
let workInProgress: Fiber | null = null;
// The lanes we're rendering
// 准备渲染的 Lane
let workInProgressRootRenderLanes: Lanes = NoLanes;
2. 处理中的值
备注
源码中 443 - 462
// When this is true, the work-in-progress fiber just suspended (or errored) and
// we've yet to unwind the stack. In some cases, we may yield to the main thread
// after this happens. If the fiber is pinged before we resume, we can retry
// immediately instead of unwinding the stack.
//
// 当这个为真时,正在进行中的 fiber 刚刚被挂起(或出错),而我们尚未清理堆栈。
// 在某些情况下,这发生后我们可能会让出给主线程。
// 如果在我们恢复之前 fiber 收到 ping,我们可以立即重试,而不是清理堆栈。
// 工作进行中暂停原因
let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
// 工作进行中抛出的值
let workInProgressThrownValue: mixed = null;
// Tracks whether any siblings were skipped during the unwind phase after
// something suspends. Used to determine whether to schedule another render
// to prewarm the skipped siblings.
//
// 跟踪在某个操作被挂起后,在展开阶段是否跳过了兄弟节点。
// 用于确定是否需要安排另一轮渲染来预热被跳过的兄弟节点。
//
// workInProgressRoot 跳过了挂起的兄弟节点
let workInProgressRootDidSkipSuspendedSiblings: boolean = false;
// Whether the work-in-progress render is the result of a prewarm/prerender.
// This tells us whether or not we should render the siblings after
// something suspends.
//
// 无论正在进行的渲染是否是预热/预渲染的结果。
// 这告诉我们当某些内容挂起时是否应该渲染兄弟节点。
//
// 正在进行的根正在预渲染
let workInProgressRootIsPrerendering: boolean = false;
// Whether a ping listener was attached during this render. This is slightly
// different that whether something suspended, because we don't add multiple
// listeners to a promise we've already seen (per root and lane).
//
// 在此渲染期间是否附加了 ping 监听器。这与某些内容是否挂起略有不同,
// 因为我们不会对已经见过的 Promise 添加多个监听器(按根和 Lane 分别计算)。
//
// workInProgressRoot 已附加 Ping 监听器
let workInProgressRootDidAttachPingListener: boolean = false;
3. 备用树的状态
备注
源码中的 474 - 523
// Whether to root completed, errored, suspended, etc.
// 是否已完成、出错、挂起等
// 工作进行中根退出状态
let workInProgressRootExitStatus: RootExitStatus = RootInProgress;
// The work left over by components that were visited during this render. Only
// includes unprocessed updates, not work in bailed out children.
//
// 在此次渲染过程中被访问的组件剩余的工作。仅包括未处理的更新,不包括已放弃的子组件中的工作。
// 工作进行中根跳过的通道
let workInProgressRootSkippedLanes: Lanes = NoLanes;
// Lanes that were updated (in an interleaved event) during this render.
// 在此渲染期间(在交错事件中)更新的 Lane
// 进行中根节点交错更新的 Lane
let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes;
// Lanes that were updated during the render phase (*not* an interleaved event).
// 在渲染阶段更新的通道(*不是*交错事件)。
// 正在进行的根渲染阶段已更新的 Lane
let workInProgressRootRenderPhaseUpdatedLanes: Lanes = NoLanes;
// Lanes that were pinged (in an interleaved event) during this render.
// 在此次渲染期间(在交错事件中)被 ping 的优先级队列。
// 正在进行中的根节点已触发的 Lane
let workInProgressRootPingedLanes: Lanes = NoLanes;
// If this render scheduled deferred work, this is the lane of the deferred task.
// 如果此渲染调度了延迟工作,这就是延迟任务的 Lane
// 工作进行中延迟 Lane
let workInProgressDeferredLane: Lane = NoLane;
// Represents the retry lanes that were spawned by this render and have not
// been pinged since, implying that they are still suspended.
//
// 表示由此渲染生成且自那时起未被触发的重试 Lane,这意味着它们仍然处于挂起状态。
// 工作进展暂停重试 Lane
let workInProgressSuspendedRetryLanes: Lanes = NoLanes;
// Errors that are thrown during the render phase.
// 在并发渲染阶段抛出的错误。
let workInProgressRootConcurrentErrors: Array<CapturedValue<mixed>> | null =
null;
// These are errors that we recovered from without surfacing them to the UI.
// We will log them once the tree commits.
//
// 这些是我们恢复过来的错误,但没有显示在用户界面上。
// 一旦树提交,我们会记录它们。
// 工作进行中根可恢复错误
let workInProgressRootRecoverableErrors: Array<CapturedValue<mixed>> | null =
null;
// Tracks when an update occurs during the render phase.
// 跟踪在渲染阶段发生更新的情况。
let workInProgressRootDidIncludeRecursiveRenderUpdate: boolean = false;
// Thacks when an update occurs during the commit phase. It's a separate
// variable from the one for renders because the commit phase may run
// concurrently to a render phase.
//
// 当在提交阶段发生更新时触发 Thacks。它是一个独立的变量,不同于渲染阶段
// 使用的变量,因为提交阶段可能与渲染阶段并发运行。
let didIncludeCommitPhaseUpdate: boolean = false;
// The most recent time we either committed a fallback, or when a fallback was
// filled in with the resolved UI. This lets us throttle the appearance of new
// content as it streams in, to minimize jank.
// TODO: Think of a better name for this variable?
//
// 我们最近一次采用回退方案,或者用解析后的 UI 填充回退时的时间。这让我们在
// 新的内容流入时能够控制其出现频率,以最小化卡顿。
// TODO: 想一个更好的变量名字?
let globalMostRecentFallbackTime: number = 0;
// Track the most recent time we started a new Transition. This lets us apply
// heuristics like the suspensey image timeout based on how long we've waited
// already.
//
// 跟踪我们最近一次开始新过渡的时间。这让我们可以根据已经等待的时间
// 来应用类似挂起图像超时的启发式方法。
let globalMostRecentTransitionTime: number = 0;
// 回退节流毫秒
const FALLBACK_THROTTLE_MS: number = 300;
// The absolute time for when we should start giving up on rendering
// more and prefer CPU suspense heuristics instead.
//
// 我们应该放弃更多渲染、转而优先使用 CPU 暂停启发式的绝对时间。
let workInProgressRootRenderTargetTime: number = Infinity;
// How long a render is supposed to take before we start following CPU
// suspense heuristics and opt out of rendering more content.
//
// 在我们开始遵循 CPU 暂停启发式并选择不再渲染更多内容之前,渲染应该持续多长时间。
const RENDER_TIMEOUT_MS = 500;
// 进行中转换
let workInProgressTransitions: Array<Transition> | null = null;
4. 工作进度
备注
源码中的 528 - 532 行
// The first setState call that eventually caused the current render.
// 第一个 setState 调用,最终导致了当前的渲染。
// 工作进度更新任务
let workInProgressUpdateTask: null | ConsoleTask = null;
// 当前待处理过渡回调
let currentPendingTransitionCallbacks: PendingTransitionCallbacks | null = null;
// 当前结束时间
let currentEndTime: number | null = null;
5. 已失败的旧版错误边界
备注
源码中的 700 行
let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
6. 待处理状态
备注
源码中 718 - 733 行
// 待处理效果状态
let pendingEffectsStatus: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0;
// 待处理效果根
let pendingEffectsRoot: FiberRoot = null as any;
// 待完成工作
let pendingFinishedWork: Fiber = null as any;
// 待处理效果 Lanes
let pendingEffectsLanes: Lanes = NoLanes;
// 待处理效果剩余 Lanes
let pendingEffectsRemainingLanes: Lanes = NoLanes;
// 待处理效果渲染结束时间 (仅限分析)
let pendingEffectsRenderEndTime: number = -0; // Profiling-only
// 待处理的被动过渡
let pendingPassiveTransitions: Array<Transition> | null = null;
// 待恢复错误
let pendingRecoverableErrors: null | Array<CapturedValue<mixed>> = null;
// 待查看过渡
let pendingViewTransition: null | RunningViewTransition = null;
// 待处理视图过渡事件
let pendingViewTransitionEvents: Array<(types: Array<string>) => void> | null =
null;
// 待处理的过渡类型
let pendingTransitionTypes: null | TransitionTypes = null;
// 待处理的渲染阶段更新
let pendingDidIncludeRenderPhaseUpdate: boolean = false;
// 待定/暂停的提交原因(仅限分析)
let pendingSuspendedCommitReason: SuspendedCommitReason = null; // Profiling-only
// 待处理延迟提交原因(仅限分析)
let pendingDelayedCommitReason: DelayedCommitReason = IMMEDIATE_COMMIT; // Profiling-only
// 挂起的暂停视图转换原因(仅限分析)
let pendingSuspendedViewTransitionReason: null | string = null; // Profiling-only
7. 更新
备注
源码中 737 - 740 行
// 嵌套更新计数
let nestedUpdateCount: number = 0;
// 带嵌套更新的根
let rootWithNestedUpdates: FiberRoot | null = null;
// 正在刷新被动效果
let isFlushingPassiveEffects = false;
// 在被动效果期间安排了更新
let didScheduleUpdateDuringPassiveEffects = false;
8. e
备注
源码中的 743 - 746
// 嵌套被动更新计数
let nestedPassiveUpdateCount: number = 0;
// 带被动嵌套更新的根
let rootWithPassiveNestedUpdates: FiberRoot | null = null;
// 正在运行插入效果
let isRunningInsertionEffect = false;
9. 已警告有关中断的视图过渡
备注
源码中 4414 行
let didWarnAboutInterruptedViewTransitions = false;
10. 尚未挂载组件的状态更新警告
备注
在源码中 5123 行
let didWarnStateUpdateForNotYetMountedComponent: Set<string> | null = null;
11. 在渲染中发出更新警告
备注
在源码中 5172 行
let didWarnAboutUpdateInRender = false;
12. 在渲染另一个组件时发出更新警告
备注
在源码中 5173 - 7176 行
let didWarnAboutUpdateInRenderForAnotherComponent;
if (__DEV__) {
didWarnAboutUpdateInRenderForAnotherComponent = new Set<string>();
}
卅二、工具
1. 重置渲染时间
function resetRenderTimer() {
workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS;
}
2. 请求重试通道
备注
claimNextRetryLane()由 ReactFiberLane#claimNextRetryLane 实现
function requestRetryLane(fiber: Fiber) {
// This is a fork of `requestUpdateLane` designed specifically for Suspense
// "retries" — a special update that attempts to flip a Suspense boundary
// from its placeholder state to its primary/resolved state.
//
// 这是 `requestUpdateLane` 的一个分支,专门为 Suspense 设计
// “重试” — 一种特殊的更新,尝试将 Suspense 边界
// 从占位状态切换到其主要/已解析状态。
// Special cases
// 特殊情况
const mode = fiber.mode;
if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) {
return SyncLane as Lane;
}
return claimNextRetryLane();
}
3. 准备新堆
备注
resetOwnerStackLimit()由 shared 提供markAllLanesInOrder()由 ReactFiberPerformanceTrack#markAllLanesInOrder 实现recordRenderTime()由 ReactProfilerTimer#recordRenderTime 实现clearGestureTimers()由 ReactProfilerTimer#clearGestureTimers 实现clearBlockingTimers()由 ReactProfilerTimer#clearBlockingTimers 实现clearTransitionTimers()由 ReactProfilerTimer#clearTransitionTimers 实现setCurrentTrackFromLanes()由 ReactFiberPerformanceTrack#setCurrentTrackFromLanes 实现logSuspendedRenderPhase()由 ReactFiberPerformanceTrack#logSuspendedRenderPhase 实现logInterruptedRenderPhase()由 ReactFiberPerformanceTrack#logInterruptedRenderPhase 实现logSuspendedWithDelayPhase()由 ReactFiberPerformanceTrack#logSuspendedWithDelayPhase 实现logAnimatingPhase()由 ReactFiberPerformanceTrack#logAnimatingPhase 实现logGestureStart()由 ReactFiberPerformanceTrack#logGestureStart 实现logBlockingStart()由 ReactFiberPerformanceTrack#logBlockingStart 实现logTransitionStart()由 ReactFiberPerformanceTrack#logTransitionStart 实现isGestureRender()由 ReactFiberLane#isGestureRender 实现includesBlockingLane()由 ReactFiberLane#includesBlockingLane 实现includesTransitionLane()由 ReactFiberLane#includesTransitionLane 实现includesRetryLane()由 ReactFiberLane#includesRetryLane 实现includesIdleGroupLanes()由 ReactFiberLane#includesIdleGroupLanes 实现cancelTimeout()由宿主环境提供
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// The order of tracks within a group are determined by the earliest start time.
// Are tracks should show up in priority order and we should ideally always show
// every track. This is a hack to ensure that we're displaying all tracks in the
// right order. Ideally we could do this only once but because calls that aren't
// recorded aren't considered for ordering purposes, we need to keep adding these
// over and over again in case recording has just started. We can't tell when
// recording starts.
//
// 组内曲目的顺序由最早的开始时间决定。
// 曲目应按优先级顺序显示,理想情况下我们应该始终显示
// 所有曲目。这是一种确保我们以正确顺序显示所有曲目的临时方法。
// 理想情况下我们只需执行一次,但由于未记录的通话在排序时不考虑,
// 我们需要不断添加这些曲目,以防录音刚刚开始。我们无法判断录音何时开始。
markAllLanesInOrder();
const previousRenderStartTime = renderStartTime;
// Starting a new render. Log the end of any previous renders and the
// blocked time before the render started.
//
// 开始新的渲染。记录任何之前渲染的结束时间以及渲染开始前的阻塞时间。
recordRenderTime();
// If this was a restart, e.g. due to an interrupting update, then there's no space
// in the track to log the cause since we'll have rendered all the way up until the
// restart so we need to clamp that.
//
// 如果这是一次重启,例如由于中断更新,那么轨迹中没有空间来记录原因,
// 因为我们已经渲染到重启为止,所以我们需要对其进行限制。
if (
workInProgressRootRenderLanes !== NoLanes &&
previousRenderStartTime > 0
) {
setCurrentTrackFromLanes(workInProgressRootRenderLanes);
if (
workInProgressRootExitStatus === RootSuspended ||
workInProgressRootExitStatus === RootSuspendedWithDelay
) {
// If the root was already suspended when it got interrupted and restarted,
// then this is considered a prewarm and not an interrupted render because
// we couldn't have shown anything anyway so it's not a bad thing that we
// got interrupted.
//
// 如果根节点在被中断和重启时已经处于挂起状态,
// 那么这被视为预热而不是中断渲染,
// 因为我们无论如何都无法显示任何内容,所以被中断并不是什么坏事。
logSuspendedRenderPhase(
previousRenderStartTime,
renderStartTime,
lanes,
workInProgressUpdateTask,
);
} else {
logInterruptedRenderPhase(
previousRenderStartTime,
renderStartTime,
lanes,
workInProgressUpdateTask,
);
}
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
}
const previousUpdateTask = workInProgressUpdateTask;
workInProgressUpdateTask = null;
if (isGestureRender(lanes)) {
workInProgressUpdateTask = gestureUpdateTask;
const clampedUpdateTime =
gestureUpdateTime >= 0 && gestureUpdateTime < gestureClampTime
? gestureClampTime
: gestureUpdateTime;
const clampedEventTime =
gestureEventTime >= 0 && gestureEventTime < gestureClampTime
? gestureClampTime
: gestureEventTime;
const clampedRenderStartTime =
// Clamp the suspended time to the first event/update.
// 将挂起的时间限制在第一次事件/更新
clampedEventTime >= 0
? clampedEventTime
: clampedUpdateTime >= 0
? clampedUpdateTime
: renderStartTime;
if (gestureSuspendedTime >= 0) {
setCurrentTrackFromLanes(GestureLane);
logSuspendedWithDelayPhase(
gestureSuspendedTime,
clampedRenderStartTime,
lanes,
workInProgressUpdateTask,
);
} else if (isGestureRender(animatingLanes)) {
// If this lane is still animating, log the time from previous render
// finishing to now as animating.
//
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(GestureLane);
logAnimatingPhase(
gestureClampTime,
clampedRenderStartTime,
animatingTask,
);
}
logGestureStart(
clampedUpdateTime,
clampedEventTime,
gestureEventType,
gestureEventRepeatTime > 0,
gestureUpdateType === PINGED_UPDATE,
renderStartTime,
gestureUpdateTask,
gestureUpdateMethodName,
gestureUpdateComponentName,
);
clearGestureTimers();
} else if (includesBlockingLane(lanes)) {
workInProgressUpdateTask = blockingUpdateTask;
const clampedUpdateTime =
blockingUpdateTime >= 0 && blockingUpdateTime < blockingClampTime
? blockingClampTime
: blockingUpdateTime;
const clampedEventTime =
blockingEventTime >= 0 && blockingEventTime < blockingClampTime
? blockingClampTime
: blockingEventTime;
// 将挂起时间限制到第一次事件/更新
const clampedRenderStartTime = // Clamp the suspended time to the first event/update.
clampedEventTime >= 0
? clampedEventTime
: clampedUpdateTime >= 0
? clampedUpdateTime
: renderStartTime;
if (blockingSuspendedTime >= 0) {
setCurrentTrackFromLanes(SyncLane);
logSuspendedWithDelayPhase(
blockingSuspendedTime,
clampedRenderStartTime,
lanes,
previousUpdateTask,
);
} else if (
!isGestureRender(animatingLanes) &&
includesBlockingLane(animatingLanes)
) {
// If this lane is still animating, log the time from previous
// render finishing to now as animating.
//
// 如果这个通道仍在动画中,将上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(SyncLane);
logAnimatingPhase(
blockingClampTime,
clampedRenderStartTime,
animatingTask,
);
}
logBlockingStart(
clampedUpdateTime,
clampedEventTime,
blockingEventType,
blockingEventRepeatTime > 0,
blockingUpdateType === SPAWNED_UPDATE,
blockingUpdateType === PINGED_UPDATE,
renderStartTime,
lanes,
blockingUpdateTask,
blockingUpdateMethodName,
blockingUpdateComponentName,
);
clearBlockingTimers();
}
if (includesTransitionLane(lanes)) {
workInProgressUpdateTask = transitionUpdateTask;
const clampedStartTime =
transitionStartTime >= 0 && transitionStartTime < transitionClampTime
? transitionClampTime
: transitionStartTime;
const clampedUpdateTime =
transitionUpdateTime >= 0 && transitionUpdateTime < transitionClampTime
? transitionClampTime
: transitionUpdateTime;
const clampedEventTime =
transitionEventTime >= 0 && transitionEventTime < transitionClampTime
? transitionClampTime
: transitionEventTime;
const clampedRenderStartTime =
// Clamp the suspended time to the first event/update.
// 将挂起的时间限制在第一次事件/更新
clampedEventTime >= 0
? clampedEventTime
: clampedUpdateTime >= 0
? clampedUpdateTime
: renderStartTime;
if (transitionSuspendedTime >= 0) {
setCurrentTrackFromLanes(SomeTransitionLane);
logSuspendedWithDelayPhase(
transitionSuspendedTime,
clampedRenderStartTime,
lanes,
workInProgressUpdateTask,
);
} else if (includesTransitionLane(animatingLanes)) {
// If this lane is still animating, log the time from previous
// render finishing to now as animating.
//
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(SomeTransitionLane);
logAnimatingPhase(
transitionClampTime,
clampedRenderStartTime,
animatingTask,
);
}
logTransitionStart(
clampedStartTime,
clampedUpdateTime,
clampedEventTime,
transitionEventType,
transitionEventRepeatTime > 0,
transitionUpdateType === PINGED_UPDATE,
renderStartTime,
transitionUpdateTask,
transitionUpdateMethodName,
transitionUpdateComponentName,
);
clearTransitionTimers();
}
if (includesRetryLane(lanes)) {
if (includesRetryLane(animatingLanes)) {
// If this lane is still animating, log the time from previous
// render finishing to now as animating.
//
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(SomeRetryLane);
logAnimatingPhase(retryClampTime, renderStartTime, animatingTask);
}
}
if (includesIdleGroupLanes(lanes)) {
if (includesIdleGroupLanes(animatingLanes)) {
// If this lane is still animating, log the time from previous
// render finishing to now as animating.
//
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(IdleLane);
logAnimatingPhase(idleClampTime, renderStartTime, animatingTask);
}
}
}
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
// The root previous suspended and scheduled a timeout to commit a fallback
// state. Now that we have additional work, cancel the timeout.
//
// 根节点之前挂起并计划了一个超时以提交回退状态。
// 现在我们有了额外的工作,取消该超时。
root.timeoutHandle = noTimeout;
cancelTimeout(timeoutHandle);
}
const cancelPendingCommit = root.cancelPendingCommit;
if (cancelPendingCommit !== null) {
root.cancelPendingCommit = null;
cancelPendingCommit();
}
pendingEffectsLanes = NoLanes;
resetWorkInProgressStack();
workInProgressRoot = root;
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgressRootRenderLanes = lanes;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
workInProgressRootDidSkipSuspendedSiblings = false;
workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
workInProgressRootDidAttachPingListener = false;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootInterleavedUpdatedLanes = NoLanes;
workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
workInProgressDeferredLane = NoLane;
workInProgressSuspendedRetryLanes = NoLanes;
workInProgressRootConcurrentErrors = null;
workInProgressRootRecoverableErrors = null;
workInProgressRootDidIncludeRecursiveRenderUpdate = false;
// Get the lanes that are entangled with whatever we're about to render. We
// track these separately so we can distinguish the priority of the render
// task from the priority of the lanes it is entangled with. For example, a
// transition may not be allowed to finish unless it includes the Sync lane,
// which is currently suspended. We should be able to render the Transition
// and Sync lane in the same batch, but at Transition priority, because the
// Sync lane already suspended.
//
// 获取与我们即将渲染的内容纠缠的通道。
// 我们单独跟踪这些通道,这样可以区分渲染任务的优先级
// 与它所纠缠的通道的优先级。例如,除非包含同步通道,
// 否则可能不允许完成一个过渡,而同步通道当前已被挂起。
// 我们应该能够在同一批次中以过渡优先级渲染过渡和同步通道,
// 因为同步通道已经挂起。
entangledRenderLanes = getEntangledLanes(root, lanes);
finishQueueingConcurrentUpdates();
if (__DEV__) {
resetOwnerStackLimit();
ReactStrictModeWarnings.discardPendingWarnings();
}
return rootWorkInProgress;
}
4. 标记根已暂停
备注
removeLanes()由 ReactFiberLane#removeLanes 实现_markRootSuspended()由 ReactFiberLane#markRootSuspended 实现
function markRootSuspended(
root: FiberRoot,
suspendedLanes: Lanes,
spawnedLane: Lane,
didAttemptEntireTree: boolean,
) {
// When suspending, we should always exclude lanes that were pinged or (more
// rarely, since we try to avoid it) updated during the render phase.
//
// 在挂起时,我们应始终排除在渲染阶段被 ping 或(更少见,因为我们尽量避免)更新的 lane。
suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
suspendedLanes = removeLanes(
suspendedLanes,
workInProgressRootInterleavedUpdatedLanes,
);
_markRootSuspended(root, suspendedLanes, spawnedLane, didAttemptEntireTree);
}
5. 完成渲染
备注
isGestureRender()由 ReactFiberLane#isGestureRender 实现includesBlockingLane()由 ReactFiberLane#includesBlockingLane 实现includesRetryLane()由 ReactFiberLane#includesRetryLane 实现includesIdleGroupLanes()由 ReactFiberLane#includesIdleGroupLanes 实现clampGestureTimers()由 ReactProfilerTimer#clampGestureTimers 实现clampBlockingTimers()由 ReactProfilerTimer#clampBlockingTimers 实现clampRetryTimers()由 ReactProfilerTimer#clampRetryTimers 实现clampIdleTimers()由 ReactProfilerTimer#clampIdleTimers 实现
function finalizeRender(lanes: Lanes, finalizationTime: number): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (isGestureRender(lanes)) {
clampGestureTimers(finalizationTime);
} else if (includesBlockingLane(lanes)) {
clampBlockingTimers(finalizationTime);
}
if (includesTransitionLane(lanes)) {
clampTransitionTimers(finalizationTime);
}
if (includesRetryLane(lanes)) {
clampRetryTimers(finalizationTime);
}
if (includesIdleGroupLanes(lanes)) {
clampIdleTimers(finalizationTime);
}
}
}
6. 重置进行中工作堆栈
备注
unwindInterruptedWork()由 ReactFiberUnwindWork#unwindInterruptedWork 实现
function resetWorkInProgressStack() {
if (workInProgress === null) return;
let interruptedWork;
if (workInProgressSuspendedReason === NotSuspended) {
// Normal case. Work-in-progress hasn't started yet. Unwind all
// its parents.
//
// 正常情况。工作正在进行中,但尚未开始。展开它的所有父项。
interruptedWork = workInProgress.return;
} else {
// Work-in-progress is in suspended state. Reset the work loop and unwind
// both the suspended fiber and all its parents.
//
// 正在进行的工作处于暂停状态。重置工作循环并回溯
// 暂停的 fiber 以及它的所有父节点。
resetSuspendedWorkLoopOnUnwind(workInProgress);
interruptedWork = workInProgress;
}
while (interruptedWork !== null) {
const current = interruptedWork.alternate;
unwindInterruptedWork(
current,
interruptedWork,
workInProgressRootRenderLanes,
);
interruptedWork = interruptedWork.return;
}
workInProgress = null;
}
7. 在展开时重置挂起的工作循环
备注
resetContextDependencies()由 ReactFiberNewContext#resetContextDependencies 实现resetHooksOnUnwind()由 ReactFiberHooks#resetHooksOnUnwind 实现resetChildReconcilerOnUnwind()由 ReactChildFiber#resetChildReconcilerOnUnwind 实现
function resetSuspendedWorkLoopOnUnwind(fiber: Fiber) {
// 重置在渲染阶段设置的模块级状态。
// Reset module-level state that was set during the render phase.
resetContextDependencies();
resetHooksOnUnwind(fiber);
resetChildReconcilerOnUnwind();
}
8. 从并发错误中恢复
备注
mergeLanes()由 ReactFiberLane#SomeRetryLane 实现
function recoverFromConcurrentError(
root: FiberRoot,
originallyAttemptedLanes: Lanes,
errorRetryLanes: Lanes,
): RootExitStatus {
// If an error occurred during hydration, discard server response and fall
// back to client side render.
//
// 如果在水化过程中发生错误,丢弃服务器响应并回退到客户端渲染。
// Before rendering again, save the errors from the previous attempt.
// 在再次渲染之前,保存上一次尝试中的错误。
const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
const wasRootDehydrated = supportsHydration && isRootDehydrated(root);
if (wasRootDehydrated) {
// The shell failed to hydrate. Set a flag to force a client rendering
// during the next attempt. To do this, we call prepareFreshStack now
// to create the root work-in-progress fiber. This is a bit weird in terms
// of factoring, because it relies on renderRootSync not calling
// prepareFreshStack again in the call below, which happens because the
// root and lanes haven't changed.
//
// 外壳未能完成水合。设置一个标志以在下一次尝试时强制客户端渲染。
// 为此,我们现在调用 prepareFreshStack,来创建根工作进行中的 fiber。在结构上这有点奇怪,
// 因为它依赖于 renderRootSync 在下面的调用中不再次调用 prepareFreshStack,这是因为根
// 和 lanes 没有变化。
//
// TODO: I think what we should do is set ForceClientRender inside
// throwException, like we do for nested Suspense boundaries. The reason
// it's here instead is so we can switch to the synchronous work loop, too.
// Something to consider for a future refactor.
//
// TODO: 我认为我们应该做的是在 throwException 内部设置 ForceClientRender,就像我们
// 对嵌套的 Suspense 边界所做的那样。它现在出现在这里的原因是我们也可以切换到同步工作循环
// 。这是未来重构时需要考虑的事情。
const rootWorkInProgress = prepareFreshStack(root, errorRetryLanes);
rootWorkInProgress.flags |= ForceClientRender;
}
const exitStatus = renderRootSync(root, errorRetryLanes, false);
if (exitStatus !== RootErrored) {
// Successfully finished rendering on retry
// 在重试后成功完成渲染
if (workInProgressRootDidAttachPingListener && !wasRootDehydrated) {
// During the synchronous render, we attached additional ping listeners.
// This is highly suggestive of an uncached promise (though it's not the
// only reason this would happen). If it was an uncached promise, then
// it may have masked a downstream error from ocurring without actually
// fixing it. Example:
//
// 在同步渲染期间,我们附加了额外的 ping 监听器。
// 这很可能表明是一个未缓存的 promise(尽管这并不是唯一可能的原因)。如果它是一个未缓存
// 的 promise,那么它可能掩盖了下游错误的发生,而实际上并没有真正解决问题。示例:
//
// use(Promise.resolve('uncached'))
// throw new Error('Oops!')
//
// When this happens, there's a conflict between blocking potential
// concurrent data races and unwrapping uncached promise values. We
// have to choose one or the other. Because the data race recovery is
// a last ditch effort, we'll disable it.
//
// 当这种情况发生时,会在阻止潜在的并发数据竞争和展开未缓存的承诺值之间产生冲突。我们
// 必须选择其一。由于数据竞争恢复是一种最后的努力,我们将禁用它。
root.errorRecoveryDisabledLanes = mergeLanes(
root.errorRecoveryDisabledLanes,
originallyAttemptedLanes,
);
// Mark the current render as suspended and force it to restart. Once
// these lanes finish successfully, we'll re-enable the error recovery
// mechanism for subsequent updates.
//
// 将当前渲染标记为挂起并强制重新启动。一旦这些线程成功完成,我们将重新启用后续更新
// 的错误恢复机制。
workInProgressRootInterleavedUpdatedLanes |= originallyAttemptedLanes;
return RootSuspendedWithDelay;
}
// The errors from the failed first attempt have been recovered. Add
// them to the collection of recoverable errors. We'll log them in the
// commit phase.
// 从第一次尝试失败中恢复了错误。将它们添加到可恢复错误的集合中。我们将在提交阶段记录它们。
const errorsFromSecondAttempt = workInProgressRootRecoverableErrors;
workInProgressRootRecoverableErrors = errorsFromFirstAttempt;
// The errors from the second attempt should be queued after the errors
// from the first attempt, to preserve the causal sequence.
// 第二次尝试中的错误应排在第一次尝试错误之后,以保持因果顺序。
if (errorsFromSecondAttempt !== null) {
queueRecoverableErrors(errorsFromSecondAttempt);
}
} else {
// The UI failed to recover.
// 用户界面恢复失败。
}
return exitStatus;
}
9. 完成并发渲染
备注
includesOnlyTransitions()由 ReactFiberLane#includesOnlyTransitions 实现includesOnlyRetries()由 ReactFiberLane#includesOnlyRetries 实现setCurrentTrackFromLanes()由 ReactFiberPerformanceTrack#setCurrentTrackFromLanes 实现logSuspendedRenderPhase()由 ReactFiberPerformanceTrack#logSuspendedRenderPhase 实现trackSuspendedTime()由 [ReactFiberPerformanceTrack#trackSuspendedTime] 实现now()由 Scheduler#now 实现getNextLanes()由 ReactFiberLane#getNextLanes 实现scheduleTimeout()由宿主环境提供
function finishConcurrentRender(
root: FiberRoot,
exitStatus: RootExitStatus,
finishedWork: Fiber,
lanes: Lanes,
// 仅供性能分析用
renderEndTime: number, // Profiling-only
) {
// TODO: The fact that most of these branches are identical suggests that some
// of the exit statuses are not best modeled as exit statuses and should be
// tracked orthogonally.
//
// 待办事项:大多数这些分支是相同的,这表明某些退出状态最好不要作为退出状态来建模,而应以独立的方式跟踪。
switch (exitStatus) {
case RootInProgress:
case RootFatalErrored: {
throw new Error('Root did not complete. This is a bug in React.');
}
case RootSuspendedWithDelay: {
if (!includesOnlyTransitions(lanes) && !includesOnlyRetries(lanes)) {
// Commit the placeholder.
// 提交占位符。
break;
}
}
// Fallthrough
case RootSuspendedAtTheShell: {
// This is a transition, so we should exit without committing a
// placeholder and without scheduling a timeout. Delay indefinitely
// until we receive more data.
//
// 这是一次转换,因此我们应该在不提交占位符和不安排超时的情况下退出。无限期延迟,直到
// 我们收到更多数据。
if (enableProfilerTimer && enableComponentPerformanceTrack) {
setCurrentTrackFromLanes(lanes);
logSuspendedRenderPhase(
renderStartTime,
renderEndTime,
lanes,
workInProgressUpdateTask,
);
finalizeRender(lanes, renderEndTime);
trackSuspendedTime(lanes, renderEndTime);
}
const didAttemptEntireTree = !workInProgressRootDidSkipSuspendedSiblings;
markRootSuspended(
root,
lanes,
workInProgressDeferredLane,
didAttemptEntireTree,
);
return;
}
case RootErrored: {
// This render errored. Ignore any recoverable errors because we weren't actually
// able to recover. Instead, whatever the final errors were is the ones we log.
// This ensures that we only log the actual client side error if it's just a plain
// error thrown from a component on the server and the client.
//
// 此渲染出错。忽略任何可恢复的错误,因为我们实际上无法恢复。相反,我们记录最终出现的错误。
// 这样可以确保我们只记录真正的客户端错误,如果它只是从服务器上的组件抛出的普通错误。
workInProgressRootRecoverableErrors = null;
break;
}
case RootSuspended:
case RootCompleted: {
break;
}
default: {
throw new Error('Unknown root exit status.');
}
}
if (shouldForceFlushFallbacksInDEV()) {
// We're inside an `act` scope. Commit immediately.
// 我们在 `act` 范围内。立即提交。
commitRoot(
root,
finishedWork,
lanes,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
workInProgressRootDidIncludeRecursiveRenderUpdate,
workInProgressDeferredLane,
workInProgressRootInterleavedUpdatedLanes,
workInProgressSuspendedRetryLanes,
exitStatus,
null,
null,
renderStartTime,
renderEndTime,
);
} else {
if (
includesOnlyRetries(lanes) &&
(alwaysThrottleRetries || exitStatus === RootSuspended)
) {
// This render only included retries, no updates. Throttle committing
// retries so that we don't show too many loading states too quickly.
//
// 这个渲染只包括重试,没有更新。限制提交重试次数,这样我们就不会过快地显示太多加载状态。
const msUntilTimeout =
globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
// Don't bother with a very short suspense time.
// 不要费心设置一个非常短的挂起时间。
if (msUntilTimeout > 10) {
const didAttemptEntireTree =
!workInProgressRootDidSkipSuspendedSiblings;
markRootSuspended(
root,
lanes,
workInProgressDeferredLane,
didAttemptEntireTree,
);
const nextLanes = getNextLanes(root, NoLanes, true);
if (nextLanes !== NoLanes) {
// There's additional work we can do on this root. We might as well
// attempt to work on that while we're suspended.
//
// 我们可以在这个根节点上做更多的工作。既然我们被挂起了,不妨顺便尝试处理它。
return;
}
// 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.
// TODO: Combine retry throttling with Suspensey commits. Right now they
// run one after the other.
//
// 渲染被挂起,它还没有超时,也没有更低优先级的工作要做。
// 与其立即提交备用内容,不如等待更多数据到达。
// TODO:将重试节流与 Suspense 风格提交结合起来。现在它们是依次运行的。
pendingEffectsLanes = lanes;
root.timeoutHandle = scheduleTimeout(
commitRootWhenReady.bind(
null,
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
workInProgressRootDidIncludeRecursiveRenderUpdate,
lanes,
workInProgressDeferredLane,
workInProgressRootInterleavedUpdatedLanes,
workInProgressSuspendedRetryLanes,
workInProgressRootDidSkipSuspendedSiblings,
exitStatus,
'Throttled',
renderStartTime,
renderEndTime,
),
msUntilTimeout,
);
return;
}
}
commitRootWhenReady(
root,
finishedWork,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
workInProgressRootDidIncludeRecursiveRenderUpdate,
lanes,
workInProgressDeferredLane,
workInProgressRootInterleavedUpdatedLanes,
workInProgressSuspendedRetryLanes,
workInProgressRootDidSkipSuspendedSiblings,
exitStatus,
null,
renderStartTime,
renderEndTime,
);
}
}
10. 准备好时提交根
备注
includesOnlyViewTransitionEligibleLanes()由 ReactFiberLane#includesOnlyViewTransitionEligibleLanes 实现isGestureRender()由 ReactFiberLane#isGestureRender 实现startSuspendingCommit()由宿主环境提供accumulateSuspenseyCommit()由 ReactFiberCommitWork#accumulateSuspenseyCommit 实现suspendOnActiveViewTransition()由宿主环境提供includesOnlyRetries()由 ReactFiberLane#includesOnlyRetries 实现now()由 Scheduler#now 实现includesOnlyTransitions()由 ReactFiberLane#includesOnlyTransitions 实现waitForCommitToBeReady()由宿主环境提供getSuspendedCommitReason()由宿主环境提供
function commitRootWhenReady(
root: FiberRoot,
finishedWork: Fiber,
recoverableErrors: Array<CapturedValue<mixed>> | null,
transitions: Array<Transition> | null,
didIncludeRenderPhaseUpdate: boolean,
lanes: Lanes,
spawnedLane: Lane,
updatedLanes: Lanes,
suspendedRetryLanes: Lanes,
didSkipSuspendedSiblings: boolean,
exitStatus: RootExitStatus,
// 仅供性能分析用
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
// 仅供性能分析用
completedRenderStartTime: number, // Profiling-only
// 仅供性能分析用
completedRenderEndTime: number, // Profiling-only
) {
root.timeoutHandle = noTimeout;
// TODO: Combine retry throttling with Suspensey commits. Right now they run
// one after the other.
//
// 待办:将重试节流与 Suspense 式提交结合起来。现在它们是依次运行的。
const BothVisibilityAndMaySuspendCommit = Visibility | MaySuspendCommit;
const subtreeFlags = finishedWork.subtreeFlags;
// 待办:使用 subtreeFlag 进行优化。
const isViewTransitionEligible =
enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes); // TODO: Use a subtreeFlag to optimize.
const isGestureTransition = enableGestureTransition && isGestureRender(lanes);
const maySuspendCommit =
subtreeFlags & ShouldSuspendCommit ||
(subtreeFlags & BothVisibilityAndMaySuspendCommit) ===
BothVisibilityAndMaySuspendCommit;
let suspendedState: null | SuspendedState = null;
if (isViewTransitionEligible || maySuspendCommit || isGestureTransition) {
// Before committing, ask the renderer whether the host tree is ready.
// If it's not, we'll wait until it notifies us.
//
// 在提交之前,询问渲染器宿主树是否已就绪。如果没有,我们将等待它通知我们。
suspendedState = startSuspendingCommit();
// This will walk the completed fiber tree and attach listeners to all
// the suspensey resources. The renderer is responsible for accumulating
// all the load events. This all happens in a single synchronous
// transaction, so it track state in its own module scope.
// This will also track any newly added or appearing ViewTransition
// components for the purposes of forming pairs.
//
// 这将遍历已完成的 fiber 树并为所有 suspense 资源附加监听器。渲染器负责累积所有的
// 加载事件。所有这些都发生在一个单一的同步事务中,因此它在自己的模块范围内跟踪状态。
// 这也将跟踪为形成配对而新添加或出现的 ViewTransition 组件。
accumulateSuspenseyCommit(finishedWork, lanes, suspendedState);
if (isViewTransitionEligible || isGestureTransition) {
// If we're stopping gestures we don't have to wait for any pending
// view transition. We'll stop it when we commit.
//
// 如果我们停止手势,就不必等待任何未完成的视图过渡。我们将在提交时停止它。
if (!enableGestureTransition || root.stoppingGestures === null) {
suspendOnActiveViewTransition(suspendedState, root.containerInfo);
}
}
// For timeouts we use the previous fallback commit for retries and
// the start time of the transition for transitions. This offset
// represents the time already passed.
//
// 对于超时,我们在重试时使用之前的备用提交,对于过渡则使用过渡的开始时间。这个
// 偏移量表示已经经过的时间。
const timeoutOffset = includesOnlyRetries(lanes)
? globalMostRecentFallbackTime - now()
: includesOnlyTransitions(lanes)
? globalMostRecentTransitionTime - now()
: 0;
// At the end, ask the renderer if it's ready to commit, or if we should
// suspend. If it's not ready, it will return a callback to subscribe to
// a ready event.
//
// 最后,询问渲染器是否准备好提交,或者我们是否应该暂停。
// 如果它尚未准备好,它将返回一个回调以订阅就绪事件。
const schedulePendingCommit = waitForCommitToBeReady(
suspendedState,
timeoutOffset,
);
if (schedulePendingCommit !== null) {
// NOTE: waitForCommitToBeReady returns a subscribe function so that we
// only allocate a function if the commit isn't ready yet. The other
// pattern would be to always pass a callback to waitForCommitToBeReady.
//
// 注意:waitForCommitToBeReady 返回一个订阅函数,这样只有在提交还未准备好时我们
// 才会分配一个函数。另一种模式是总是将回调传递给 waitForCommitToBeReady。
// Not yet ready to commit. Delay the commit until the renderer notifies
// us that it's ready. This will be canceled if we start work on the
// root again.
//
// 尚未准备好提交。在渲染器通知我们它已准备好之前,延迟提交。
// 如果我们再次开始处理根节点,这将被取消。
pendingEffectsLanes = lanes;
root.cancelPendingCommit = schedulePendingCommit(
commitRoot.bind(
null,
root,
finishedWork,
lanes,
recoverableErrors,
transitions,
didIncludeRenderPhaseUpdate,
spawnedLane,
updatedLanes,
suspendedRetryLanes,
exitStatus,
suspendedState,
enableProfilerTimer
? getSuspendedCommitReason(suspendedState, root.containerInfo)
: null,
completedRenderStartTime,
completedRenderEndTime,
),
);
const didAttemptEntireTree = !didSkipSuspendedSiblings;
markRootSuspended(root, lanes, spawnedLane, didAttemptEntireTree);
return;
}
}
// Otherwise, commit immediately.;
// 否则,立即提交。
commitRoot(
root,
finishedWork,
lanes,
recoverableErrors,
transitions,
didIncludeRenderPhaseUpdate,
spawnedLane,
updatedLanes,
suspendedRetryLanes,
exitStatus,
suspendedState,
suspendedCommitReason,
completedRenderStartTime,
completedRenderEndTime,
);
}
11. 判定是否渲染与外部存储一致
function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean {
// Search the rendered tree for external store reads, and check whether the
// stores were mutated in a concurrent event. Intentionally using an iterative
// loop instead of recursion so we can exit early.
//
// 在渲染后的树中搜索外部存储读取,并检查这些存储是否在并发事件中被修改。故意使用迭代循环
// 而不是递归,以便我们可以尽早退出。
let node: Fiber = finishedWork;
while (true) {
const tag = node.tag;
if (
(tag === FunctionComponent ||
tag === ForwardRef ||
tag === SimpleMemoComponent) &&
node.flags & StoreConsistency
) {
const updateQueue: FunctionComponentUpdateQueue | null =
node.updateQueue as any;
if (updateQueue !== null) {
const checks = updateQueue.stores;
if (checks !== null) {
for (let i = 0; i < checks.length; i++) {
const check = checks[i];
const getSnapshot = check.getSnapshot;
const renderedValue = check.value;
try {
if (!is(getSnapshot(), renderedValue)) {
// Found an inconsistent store.
// 发现了一个不一致的商店。
return false;
}
} catch (error) {
// If `getSnapshot` throws, return `false`. This will schedule
// a re-render, and the error will be rethrown during render.
//
// 如果 `getSnapshot` 抛出异常,返回 `false`。这将安排重新渲染,并且
// 错误将在渲染期间重新抛出。
return false;
}
}
}
}
}
const child = node.child;
if (node.subtreeFlags & StoreConsistency && child !== null) {
child.return = node;
node = child;
continue;
}
if (node === finishedWork) {
return true;
}
while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return true;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
// Flow doesn't know this is unreachable, but eslint does
//
// Flow 不知道这是不可达的,但 eslint 知道
return true;
}
12. 标记根已更新
备注
_markRootUpdated()由 ReactFiberLane#markRootUpdated 实现
// The extra indirections around markRootUpdated and markRootSuspended is
// needed to avoid a circular dependency between this module and
// ReactFiberLane. There's probably a better way to split up these modules and
// avoid this problem. Perhaps all the root-marking functions should move into
// the work loop.
//
// 在 markRootUpdated 和 markRootSuspended 周围的额外间接调用是必要的,
// 以避免此模块与 ReactFiberLane 之间的循环依赖。可能有更好的方法来拆分这些模块并避免这个问题。
// 也许所有的根标记函数都应该移到工作循环中。
function markRootUpdated(root: FiberRoot, updatedLanes: Lanes) {
_markRootUpdated(root, updatedLanes);
if (enableInfiniteRenderLoopDetection) {
// Check for recursive updates
// 检查递归更新
if (executionContext & RenderContext) {
workInProgressRootDidIncludeRecursiveRenderUpdate = true;
} else if (executionContext & CommitContext) {
didIncludeCommitPhaseUpdate = true;
}
throwIfInfiniteUpdateLoopDetected();
}
}
13. 标记根已 ping
备注
_markRootPinged()由 ReactFiberLane#markRootPinged 实现
function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
_markRootPinged(root, pingedLanes);
if (enableInfiniteRenderLoopDetection) {
// Check for recursive pings. Pings are conceptually different from updates in
// other contexts but we call it an "update" in this context because
// repeatedly pinging a suspended render can cause a recursive render loop.
// The relevant property is that it can result in a new render attempt
// being scheduled.
//
// 检查递归 ping。ping 在概念上与其他上下文中的更新不同,
// 但在本上下文中我们称其为“更新”,因为反复 ping 一个已挂起的渲染可能会导致递归渲染循环。
// 相关属性是,它可能会导致调度新的渲染尝试。
if (executionContext & RenderContext) {
workInProgressRootDidIncludeRecursiveRenderUpdate = true;
} else if (executionContext & CommitContext) {
didIncludeCommitPhaseUpdate = true;
}
throwIfInfiniteUpdateLoopDetected();
}
}
14. 准备新堆
备注
markAllLanesInOrder()由 ReactFiberPerformanceTrack#markAllLanesInOrder 实现recordRenderTime()由 ReactProfilerTimer#recordRenderTime 实现setCurrentTrackFromLanes()由 ReactFiberPerformanceTrack#setCurrentTrackFromLanes 实现logSuspendedRenderPhase()由 ReactFiberPerformanceTrack#logSuspendedRenderPhase 实现logInterruptedRenderPhase()由 ReactFiberPerformanceTrack#logInterruptedRenderPhase 实现isGestureRender()由 ReactFiberLane#isGestureRender 实现logSuspendedWithDelayPhase()由 ReactFiberPerformanceTrack#logSuspendedWithDelayPhase 实现logAnimatingPhase()由 ReactFiberPerformanceTrack#logAnimatingPhase 实现logGestureStart()由 ReactFiberPerformanceTrack#logGestureStart 实现clearGestureTimers()由 ReactProfilerTimer#clearGestureTimers 实现includesBlockingLane()由 ReactFiberLane#includesBlockingLane 实现logBlockingStart()由 ReactFiberPerformanceTrack#logBlockingStart 实现clearBlockingTimers()由 ReactProfilerTimer#clearBlockingTimers 实现includesTransitionLane()由 ReactFiberLane#includesTransitionLane 实现logTransitionStart()由 ReactFiberPerformanceTrack#logTransitionStart 实现clearTransitionTimers()由 ReactProfilerTimer#clearTransitionTimers 实现includesRetryLane()由 ReactFiberLane#includesRetryLane 实现cancelTimeout()由宿主环境提供createWorkInProgress()由 ReactFiber 提供checkIfRootIsPrerendering()由 ReactFiberLane#checkIfRootIsPrerendering 实现getEntangledLanes()由 ReactFiberLane#getEntangledLanes 实现finishQueueingConcurrentUpdates()由 ReactFiberConcurrentUpdates#finishQueueingConcurrentUpdates 实现
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// The order of tracks within a group are determined by the earliest start time.
// Are tracks should show up in priority order and we should ideally always show
// every track. This is a hack to ensure that we're displaying all tracks in the
// right order. Ideally we could do this only once but because calls that aren't
// recorded aren't considered for ordering purposes, we need to keep adding these
// over and over again in case recording has just started. We can't tell when
// recording starts.
//
// 组内曲目的顺序由最早的开始时间决定。曲目应按优先级顺序显示,理想情况下我们应该始终显示所有曲目。
// 这是一种确保我们以正确顺序显示所有曲目的临时方法。理想情况下我们只需执行一次,但由于未记录的
// 通话在排序时不考虑,我们需要不断添加这些曲目,以防录音刚刚开始。我们无法判断录音何时开始。
markAllLanesInOrder();
const previousRenderStartTime = renderStartTime;
// Starting a new render. Log the end of any previous renders and the
// blocked time before the render started.
// 开始新的渲染。记录任何之前渲染的结束时间以及渲染开始前的阻塞时间。
recordRenderTime();
// If this was a restart, e.g. due to an interrupting update, then there's no space
// in the track to log the cause since we'll have rendered all the way up until the
// restart so we need to clamp that.
//
// 如果这是一次重启,例如由于中断更新,那么轨迹中没有空间来记录原因,因为我们已经渲染到重启为止,所以
// 我们需要进行限制。
if (
workInProgressRootRenderLanes !== NoLanes &&
previousRenderStartTime > 0
) {
setCurrentTrackFromLanes(workInProgressRootRenderLanes);
if (
workInProgressRootExitStatus === RootSuspended ||
workInProgressRootExitStatus === RootSuspendedWithDelay
) {
// If the root was already suspended when it got interrupted and restarted,
// then this is considered a prewarm and not an interrupted render because
// we couldn't have shown anything anyway so it's not a bad thing that we
// got interrupted.
//
// 如果根节点在被中断并重新启动时已经处于挂起状态,那么这被视为预热而不是中断渲染,
// 因为我们无论如何都无法显示任何内容,所以被中断并不是什么坏事。
logSuspendedRenderPhase(
previousRenderStartTime,
renderStartTime,
lanes,
workInProgressUpdateTask,
);
} else {
logInterruptedRenderPhase(
previousRenderStartTime,
renderStartTime,
lanes,
workInProgressUpdateTask,
);
}
finalizeRender(workInProgressRootRenderLanes, renderStartTime);
}
const previousUpdateTask = workInProgressUpdateTask;
workInProgressUpdateTask = null;
if (isGestureRender(lanes)) {
workInProgressUpdateTask = gestureUpdateTask;
const clampedUpdateTime =
gestureUpdateTime >= 0 && gestureUpdateTime < gestureClampTime
? gestureClampTime
: gestureUpdateTime;
const clampedEventTime =
gestureEventTime >= 0 && gestureEventTime < gestureClampTime
? gestureClampTime
: gestureEventTime;
const clampedRenderStartTime =
// Clamp the suspended time to the first event/update.
// 将挂起时间限制到第一次事件/更新
clampedEventTime >= 0
? clampedEventTime
: clampedUpdateTime >= 0
? clampedUpdateTime
: renderStartTime;
if (gestureSuspendedTime >= 0) {
setCurrentTrackFromLanes(GestureLane);
logSuspendedWithDelayPhase(
gestureSuspendedTime,
clampedRenderStartTime,
lanes,
workInProgressUpdateTask,
);
} else if (isGestureRender(animatingLanes)) {
// If this lane is still animating, log the time from previous render finishing to now as animating.
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(GestureLane);
logAnimatingPhase(
gestureClampTime,
clampedRenderStartTime,
animatingTask,
);
}
logGestureStart(
clampedUpdateTime,
clampedEventTime,
gestureEventType,
gestureEventRepeatTime > 0,
gestureUpdateType === PINGED_UPDATE,
renderStartTime,
gestureUpdateTask,
gestureUpdateMethodName,
gestureUpdateComponentName,
);
clearGestureTimers();
} else if (includesBlockingLane(lanes)) {
workInProgressUpdateTask = blockingUpdateTask;
const clampedUpdateTime =
blockingUpdateTime >= 0 && blockingUpdateTime < blockingClampTime
? blockingClampTime
: blockingUpdateTime;
const clampedEventTime =
blockingEventTime >= 0 && blockingEventTime < blockingClampTime
? blockingClampTime
: blockingEventTime;
// 将挂起时间限制到第一次事件/更新
const clampedRenderStartTime = // Clamp the suspended time to the first event/update.
clampedEventTime >= 0
? clampedEventTime
: clampedUpdateTime >= 0
? clampedUpdateTime
: renderStartTime;
if (blockingSuspendedTime >= 0) {
setCurrentTrackFromLanes(SyncLane);
logSuspendedWithDelayPhase(
blockingSuspendedTime,
clampedRenderStartTime,
lanes,
previousUpdateTask,
);
} else if (
!isGestureRender(animatingLanes) &&
includesBlockingLane(animatingLanes)
) {
// If this lane is still animating, log the time from previous render finishing to now as animating.
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(SyncLane);
logAnimatingPhase(
blockingClampTime,
clampedRenderStartTime,
animatingTask,
);
}
logBlockingStart(
clampedUpdateTime,
clampedEventTime,
blockingEventType,
blockingEventRepeatTime > 0,
blockingUpdateType === SPAWNED_UPDATE,
blockingUpdateType === PINGED_UPDATE,
renderStartTime,
lanes,
blockingUpdateTask,
blockingUpdateMethodName,
blockingUpdateComponentName,
);
clearBlockingTimers();
}
if (includesTransitionLane(lanes)) {
workInProgressUpdateTask = transitionUpdateTask;
const clampedStartTime =
transitionStartTime >= 0 && transitionStartTime < transitionClampTime
? transitionClampTime
: transitionStartTime;
const clampedUpdateTime =
transitionUpdateTime >= 0 && transitionUpdateTime < transitionClampTime
? transitionClampTime
: transitionUpdateTime;
const clampedEventTime =
transitionEventTime >= 0 && transitionEventTime < transitionClampTime
? transitionClampTime
: transitionEventTime;
const clampedRenderStartTime =
// Clamp the suspended time to the first event/update.
// 将挂起时间限制到第一次事件/更新
clampedEventTime >= 0
? clampedEventTime
: clampedUpdateTime >= 0
? clampedUpdateTime
: renderStartTime;
if (transitionSuspendedTime >= 0) {
setCurrentTrackFromLanes(SomeTransitionLane);
logSuspendedWithDelayPhase(
transitionSuspendedTime,
clampedRenderStartTime,
lanes,
workInProgressUpdateTask,
);
} else if (includesTransitionLane(animatingLanes)) {
// If this lane is still animating, log the time from previous render finishing to now as animating.
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(SomeTransitionLane);
logAnimatingPhase(
transitionClampTime,
clampedRenderStartTime,
animatingTask,
);
}
logTransitionStart(
clampedStartTime,
clampedUpdateTime,
clampedEventTime,
transitionEventType,
transitionEventRepeatTime > 0,
transitionUpdateType === PINGED_UPDATE,
renderStartTime,
transitionUpdateTask,
transitionUpdateMethodName,
transitionUpdateComponentName,
);
clearTransitionTimers();
}
if (includesRetryLane(lanes)) {
if (includesRetryLane(animatingLanes)) {
// If this lane is still animating, log the time from previous render finishing to now as animating.
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(SomeRetryLane);
logAnimatingPhase(retryClampTime, renderStartTime, animatingTask);
}
}
if (includesIdleGroupLanes(lanes)) {
if (includesIdleGroupLanes(animatingLanes)) {
// If this lane is still animating, log the time from previous render finishing to now as animating.
// 如果这个通道仍在动画中,将从上一次渲染完成到现在的时间记录为动画时间。
setCurrentTrackFromLanes(IdleLane);
logAnimatingPhase(idleClampTime, renderStartTime, animatingTask);
}
}
}
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
// The root previous suspended and scheduled a timeout to commit a fallback
// state. Now that we have additional work, cancel the timeout.
//
// 根节点之前挂起并计划了一个超时来提交回退状态。
// 现在我们有了额外的工作,取消该超时。
root.timeoutHandle = noTimeout;
cancelTimeout(timeoutHandle);
}
const cancelPendingCommit = root.cancelPendingCommit;
if (cancelPendingCommit !== null) {
root.cancelPendingCommit = null;
cancelPendingCommit();
}
pendingEffectsLanes = NoLanes;
resetWorkInProgressStack();
workInProgressRoot = root;
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgressRootRenderLanes = lanes;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
workInProgressRootDidSkipSuspendedSiblings = false;
workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
workInProgressRootDidAttachPingListener = false;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootInterleavedUpdatedLanes = NoLanes;
workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
workInProgressDeferredLane = NoLane;
workInProgressSuspendedRetryLanes = NoLanes;
workInProgressRootConcurrentErrors = null;
workInProgressRootRecoverableErrors = null;
workInProgressRootDidIncludeRecursiveRenderUpdate = false;
// Get the lanes that are entangled with whatever we're about to render. We
// track these separately so we can distinguish the priority of the render
// task from the priority of the lanes it is entangled with. For example, a
// transition may not be allowed to finish unless it includes the Sync lane,
// which is currently suspended. We should be able to render the Transition
// and Sync lane in the same batch, but at Transition priority, because the
// Sync lane already suspended.
//
// 获取与我们即将渲染的内容纠缠的通道。我们单独跟踪这些通道,这样可以区分渲染任务的优先级
// 与它所纠缠的通道的优先级。例如,除非包含同步通道,否则可能不允许完成一个过渡,而同步通道
// 当前已被挂起。我们应该能够在同一批次中以过渡优先级渲染过渡和同步通道,因为同步通道已经挂起。
entangledRenderLanes = getEntangledLanes(root, lanes);
finishQueueingConcurrentUpdates();
if (__DEV__) {
resetOwnerStackLimit();
ReactStrictModeWarnings.discardPendingWarnings();
}
return rootWorkInProgress;
}
15. 处理抛出
备注
resetHooksAfterThrow()由 ReactFiberHooks#resetHooksAfterThrow 实现resetCurrentFiber()由 ReactCurrentFiber#resetCurrentFiber 实现getSuspendedThenable()由 ReactFiberThenable#getSuspendedThenable 实现logUncaughtError()由 ReactFiberErrorLogger#logUncaughtError 实现createCapturedValueAtFiber()由 ReactCapturedValue#createCapturedValueAtFiber 实现stopProfilerTimerIfRunningAndRecordDuration()由 ReactProfilerTimer#stopProfilerTimerIfRunningAndRecordDuration 实现markComponentRenderStopped()由 ReactFiberDevToolsHook#markComponentRenderStopped 实现markComponentErrored()由 ReactFiberDevToolsHook#markComponentErrored 实现markComponentSuspended()由 ReactFiberDevToolsHook#markComponentSuspended 实现
function handleThrow(root: FiberRoot, thrownValue: any): void {
// A component threw an exception. Usually this is because it suspended, but
// it also includes regular program errors.
//
// 组件抛出了一个异常。通常这是因为它被挂起,但也可能包括普通的程序错误。
//
// We're either going to unwind the stack to show a Suspense or error
// boundary, or we're going to replay the component again. Like after a
// promise resolves.
//
// 我们要么会展开堆栈以显示 Suspense 或错误边界,要么会重新渲染组件。就像在
// 一个 Promise 解决之后一样。
//
// Until we decide whether we're going to unwind or replay, we should preserve
// the current state of the work loop without resetting anything.
//
// 在我们决定是要回退还是重放之前,我们应该保留当前工作循环的状态,而不重置任何内容。
//
// If we do decide to unwind the stack, module-level variables will be reset
// in resetSuspendedWorkLoopOnUnwind.
//
// 如果我们确实决定展开堆栈,模块级变量将在 resetSuspendedWorkLoopOnUnwind 中被重置。
// These should be reset immediately because they're only supposed to be set
// when React is executing user code.
//
// 这些应该立即重置,因为它们只应在 React 执行用户代码时设置。
resetHooksAfterThrow();
if (__DEV__) {
resetCurrentFiber();
}
if (
thrownValue === SuspenseException ||
thrownValue === SuspenseActionException
) {
// This is a special type of exception used for Suspense. For historical
// reasons, the rest of the Suspense implementation expects the thrown value
// to be a thenable, because before `use` existed that was the (unstable)
// API for suspending. This implementation detail can change later, once we
// deprecate the old API in favor of `use`.
//
// 这是一种用于 Suspense 的特殊类型的异常。出于历史原因,Suspense 实现的其余部分期望抛出的
// 值是一个 thenable,因为在 `use` 出现之前,那是用于挂起的(不稳定的)API。一旦我们废弃旧
// 的 API 转而使用 `use`,这个实现细节以后可能会改变。
thrownValue = getSuspendedThenable();
// TODO: Suspending the work loop during the render phase is
// currently not compatible with sibling prerendering. We will add
// this optimization back in a later step.
// Don't suspend work loop, except to check if the data has
// immediately resolved (i.e. in a microtask). Otherwise, trigger the
// nearest Suspense fallback.
//
// TODO:在渲染阶段挂起工作循环目前与兄弟节点的预渲染不兼容。我们将在后续步骤中重新加入这个
// 优化。除了检查数据是否已经立即解析(即在微任务中)之外,不要挂起工作循环。否则,触发最近
// 的 Suspense 回退。
workInProgressSuspendedReason = SuspendedOnImmediate;
} else if (thrownValue === SuspenseyCommitException) {
thrownValue = getSuspendedThenable();
workInProgressSuspendedReason = SuspendedOnInstance;
} else if (thrownValue === SelectiveHydrationException) {
// An update flowed into a dehydrated boundary. Before we can apply the
// update, we need to finish hydrating. Interrupt the work-in-progress
// render so we can restart at the hydration lane.
//
// 一个更新流入了一个脱水的边界。在我们可以应用更新之前,我们需要完成水化。中断正在进行的
// 渲染以便我们可以在水化通道重新启动。
//
// The ideal implementation would be able to switch contexts without
// unwinding the current stack.
//
// 理想的实现应该能够在不展开当前栈的情况下切换上下文。
//
// We could name this something more general but as of now it's the only
// case where we think this should happen.
//
// 我们可以给它起一个更通用的名字,但目前这是唯一我们认为应该发生这种情况的情况。
workInProgressSuspendedReason = SuspendedOnHydration;
} else {
// This is a regular error.
// 这是一个常规错误。
const isWakeable =
thrownValue !== null &&
typeof thrownValue === 'object' &&
typeof thrownValue.then === 'function';
workInProgressSuspendedReason = isWakeable
? // A wakeable object was thrown by a legacy Suspense implementation.
// This has slightly different behavior than suspending with `use`.
//
// 一个可唤醒的对象被旧版 Suspense 实现抛出。这与使用 `use` 暂停的行为略有不同。
SuspendedOnDeprecatedThrowPromise
: // This is a regular error. If something earlier in the component already
// suspended, we must clear the thenable state to unblock the work loop.
//
// 这是一个常规错误。如果组件中之前有部分已经挂起,我们必须清除 thenable 状态以
// 解除工作循环的阻塞。
SuspendedOnError;
}
workInProgressThrownValue = thrownValue;
const erroredWork = workInProgress;
if (erroredWork === null) {
// This is a fatal error
// 这是一个致命错误
workInProgressRootExitStatus = RootFatalErrored;
logUncaughtError(
root,
createCapturedValueAtFiber(thrownValue, root.current),
);
return;
}
if (enableProfilerTimer && erroredWork.mode & ProfileMode) {
// Record the time spent rendering before an error was thrown. This
// avoids inaccurate Profiler durations in the case of a
// suspended render.
//
// 在抛出错误之前记录渲染所花费的时间。
// 这样可以避免在渲染被挂起的情况下导致 Profiler 时长不准确。
stopProfilerTimerIfRunningAndRecordDuration(erroredWork);
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
switch (workInProgressSuspendedReason) {
case SuspendedOnError: {
markComponentErrored(
erroredWork,
thrownValue,
workInProgressRootRenderLanes,
);
break;
}
case SuspendedOnData:
case SuspendedOnAction:
case SuspendedOnImmediate:
case SuspendedOnDeprecatedThrowPromise:
case SuspendedAndReadyToContinue: {
const wakeable: Wakeable = thrownValue as any;
markComponentSuspended(
erroredWork,
wakeable,
workInProgressRootRenderLanes,
);
break;
}
case SuspendedOnInstance: {
// This is conceptually like a suspend, but it's not associated with
// a particular wakeable. It's associated with a host resource (e.g.
// a CSS file or an image) that hasn't loaded yet. DevTools doesn't
// handle this currently.
//
// 这在概念上类似于挂起,但它不与特定的可唤醒对象相关联。它与尚未加载的宿主环境资源
// (例如 CSS 文件或图像)相关联。DevTools 目前不处理这个情况。
break;
}
case SuspendedOnHydration: {
// This is conceptually like a suspend, but it's not associated with
// a particular wakeable. DevTools doesn't seem to care about this case,
// currently. It's similar to if the component were interrupted, which
// we don't mark with a special function.
// 从概念上来说,这有点像挂起,但它没有关联到特定的可唤醒对象。DevTools 目前似乎不
// 在意这种情况。它类似于组件被中断的情况,我们不会用特殊函数来标记它。
break;
}
}
}
}
16. 推送调度器
function pushDispatcher(container: any) {
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = ContextOnlyDispatcher;
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.
//
// React 同构包不包含默认的调度器。相反,第一个渲染器会懒加载一个调度器,以便提供更
// 友好的错误信息。
return ContextOnlyDispatcher;
} else {
return prevDispatcher;
}
}
17. 弹出调度器
function popDispatcher(prevDispatcher: any) {
ReactSharedInternals.H = prevDispatcher;
}
18. 推送异步调度器
function pushAsyncDispatcher() {
const prevAsyncDispatcher = ReactSharedInternals.A;
ReactSharedInternals.A = DefaultAsyncDispatcher;
return prevAsyncDispatcher;
}
19. 弹出异步调度器
function popAsyncDispatcher(prevAsyncDispatcher: any) {
ReactSharedInternals.A = prevAsyncDispatcher;
}
20. 渲染根同步
备注
movePendingFibersToMemoized()由 ReactFiberLane#movePendingFibersToMemoized 实现getTransitionsForLanes()由 ReactFiberLane#getTransitionsForLanes 实现markRenderStarted()由 ReactFiberDevToolsHook#markRenderStarted 实现getSuspenseHandler()由 ReactFiberSuspenseContext#getSuspenseHandler 实现resetContextDependencies()由 ReactFiberNewContext#resetContextDependencies 实现markRenderStopped()由 ReactFiberDevToolsHook#markRenderStopped 实现finishQueueingConcurrentUpdates()由 ReactFiberConcurrentUpdates#finishQueueingConcurrentUpdates 实现
// TODO: Over time, this function and renderRootConcurrent have become more
// and more similar. Not sure it makes sense to maintain forked paths. Consider
// unifying them again.
//
// 待办事项:随着时间的推移,这个函数和 renderRootConcurrent 变得越来越相似。目前不确定
// 是否还需要维持分叉路径。可以考虑再次将它们统一。
function renderRootSync(
root: FiberRoot,
lanes: Lanes,
shouldYieldForPrerendering: boolean,
): RootExitStatus {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root.containerInfo);
const prevAsyncDispatcher = pushAsyncDispatcher();
// If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
// 如果根节点或车道发生了变化,就丢弃现有的堆栈。并准备一个新的。否则我们将从上次中断的地方继续。
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
const memoizedUpdaters = root.memoizedUpdaters;
if (memoizedUpdaters.size > 0) {
restorePendingUpdaters(root, workInProgressRootRenderLanes);
memoizedUpdaters.clear();
}
// At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
// If we bailout on this work, we'll move them back (like above).
// It's important to move them now in case the work spawns more work at the same
// priority with different updaters.
// That way we can keep the current update and future updates separate.
//
// 此时,将调度即将进行的工作的 Fiber 从 Map 移动到 Set。
// 如果我们在这项工作上中途退出,我们会将它们移回去(如上所示)。
// 现在移动它们很重要,以防这项工作生成同一优先级但由不同更新器触发的更多工作。
// 这样我们就可以将当前更新和未来更新区分开来。
movePendingFibersToMemoized(root, lanes);
}
}
workInProgressTransitions = getTransitionsForLanes(root, lanes);
prepareFreshStack(root, lanes);
}
if (enableSchedulingProfiler) {
markRenderStarted(lanes);
}
let didSuspendInShell = false;
let exitStatus = workInProgressRootExitStatus;
outer: do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
workInProgress !== null
) {
// The work loop is suspended. During a synchronous render, we don't
// yield to the main thread. Immediately unwind the stack. This will
// trigger either a fallback or an error boundary.
// TODO: For discrete and "default" updates (anything that's not
// flushSync), we want to wait for the microtasks the flush before
// unwinding. Will probably implement this using renderRootConcurrent,
// or merge renderRootSync and renderRootConcurrent into the same
// function and fork the behavior some other way.
//
// 工作循环被暂停。在同步渲染期间,我们不会让出主线程。立即展开堆栈。这将触发回退
// 或错误边界。
// TODO:对于离散和“默认”更新(任何不是 flushSync 的更新),我们希望在展开堆栈之前
// 等待微任务的刷新。可能会使用 renderRootConcurrent 来实现,或者将 renderRootSync
// 和 renderRootConcurrent 合并到同一个函数中,并通过其他方式分叉行为。
const unitOfWork = workInProgress;
const thrownValue = workInProgressThrownValue;
switch (workInProgressSuspendedReason) {
case SuspendedOnHydration: {
// Selective hydration. An update flowed into a dehydrated tree.
// Interrupt the current render so the work loop can switch to the
// hydration lane.
// TODO: I think we might not need to reset the stack here; we can
// just yield and reset the stack when we re-enter the work loop,
// like normal.
//
// 选择性水合。更新流入一个脱水的树。中断当前的渲染,以便工作循环可以切换到水合
// 通道。
// 待办事项:我认为我们可能不需要在这里重置栈;我们可以在重新进入工作循环时像
// 平常一样让出并重置栈。
resetWorkInProgressStack();
exitStatus = RootSuspendedAtTheShell;
break outer;
}
case SuspendedOnImmediate:
case SuspendedOnData:
case SuspendedOnAction:
case SuspendedOnDeprecatedThrowPromise: {
if (getSuspenseHandler() === null) {
didSuspendInShell = true;
}
const reason = workInProgressSuspendedReason;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue, reason);
if (
shouldYieldForPrerendering &&
workInProgressRootIsPrerendering
) {
// We've switched into prerendering mode. This implies that we
// suspended outside of a Suspense boundary, which means this
// render will be blocked from committing. Yield to the main
// thread so we can switch to prerendering using the concurrent
// work loop.
// 我们已切换到预渲染模式。这意味着我们在 Suspense 边界之外被挂起,
// 这意味着此次渲染将被阻止提交。让出主线程,以便我们可以使用并发工作循环
// 切换到预渲染。
exitStatus = RootInProgress;
break outer;
}
break;
}
default: {
// Unwind then continue with the normal work loop.
// 放松一下,然后继续正常的工作循环。
const reason = workInProgressSuspendedReason;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(root, unitOfWork, thrownValue, reason);
break;
}
}
}
workLoopSync();
exitStatus = workInProgressRootExitStatus;
break;
} catch (thrownValue) {
handleThrow(root, thrownValue);
}
} while (true);
// Check if something suspended in the shell. We use this to detect an
// infinite ping loop caused by an uncached promise.
//
// 检查是否有东西被挂起在 shell 中。我们用它来检测由未缓存的 promise 引起的
// 无限 ping 循环。
//
// Only increment this counter once per synchronous render attempt across the
// whole tree. Even if there are many sibling components that suspend, this
// counter only gets incremented once.
//
// 在整个树的每次同步渲染尝试中,此计数器只增加一次。即使有许多兄弟组件挂起,此计数器
// 也只会增加一次。
if (didSuspendInShell) {
root.shellSuspendCounter++;
}
resetContextDependencies();
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
popAsyncDispatcher(prevAsyncDispatcher);
if (enableSchedulingProfiler) {
markRenderStopped();
}
if (workInProgress !== null) {
// Did not complete the tree. This can happen if something suspended in
// the shell.
// 未完成树形结构。如果某些操作在 shell 中被挂起,可能会发生这种情况。
} else {
// Normal case. We completed the whole tree.
// 正常情况。我们完成了整个树。
// Set this to null to indicate there's no in-progress render.
// 将此设置为 null 表示没有正在进行的渲染。
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
// It's safe to process the queue now that the render phase is complete.
// 现在渲染阶段完成后,处理队列是安全的。
finishQueueingConcurrentUpdates();
}
return exitStatus;
}
21. 工作循环同步
// The work loop is an extremely hot path. Tell Closure not to inline it.
// 这个工作循环是一个极其热的路径。告诉 Closure 不要将其内联。
/** @noinline */
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.
// 在纤程之间执行工作时不检查是否需要让步。
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
22. 渲染根并发
备注
movePendingFibersToMemoized()由 ReactFiberLane#movePendingFibersToMemoized 实现getTransitionsForLanes()由 ReactFiberLane#getTransitionsForLanes 实现checkIfRootIsPrerendering()由 ReactFiberLane#checkIfRootIsPrerendering 实现markRenderStarted()由 ReactFiberDevToolsHook#markRenderStarted 实现isThenableResolved()由 ReactFiberThenable#isThenableResolved 实现ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现preloadResource()由宿主环境提供preloadInstance()由宿主环境提供resetContextDependencies()由 ReactFiberNewContext#resetContextDependencies 实现markRenderYielded()由 ReactFiberDevToolsHook#markRenderYielded 实现markRenderStopped()由 ReactFiberDevToolsHook#markRenderStopped 实现finishQueueingConcurrentUpdates()由 ReactFiberConcurrentUpdates#finishQueueingConcurrentUpdates 实现
function renderRootConcurrent(root: FiberRoot, lanes: Lanes): RootExitStatus {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root.containerInfo);
const prevAsyncDispatcher = pushAsyncDispatcher();
// If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
//
// 如果根节点或车道发生了变化,就丢弃现有的堆栈并准备一个新的。否则我们将从上次中断的地方继续。
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
const memoizedUpdaters = root.memoizedUpdaters;
if (memoizedUpdaters.size > 0) {
restorePendingUpdaters(root, workInProgressRootRenderLanes);
memoizedUpdaters.clear();
}
// At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
// If we bailout on this work, we'll move them back (like above).
// It's important to move them now in case the work spawns more work at the same priority with different updaters.
// That way we can keep the current update and future updates separate.
//
// 在此阶段,将计划好的即将执行的工作对应的 Fiber 从 Map 移动到 Set。
// 如果我们对这项工作中止(bailout),我们会像上面一样将它们移回。
// 现在移动它们非常重要,以防这项工作生成更多具有相同优先级但不同更新器的工作。
// 这样我们就可以将当前更新和未来更新区分开来。
movePendingFibersToMemoized(root, lanes);
movePendingFibersToMemoized(root, lanes);
}
}
workInProgressTransitions = getTransitionsForLanes(root, lanes);
resetRenderTimer();
prepareFreshStack(root, lanes);
} else {
// This is a continuation of an existing work-in-progress.
// 这是一个正在进行中的工作的延续。
//
// If we were previously in prerendering mode, check if we received any new
// data during an interleaved event.
// 如果我们之前处于预渲染模式,请检查在交错事件期间是否接收到任何新数据。
workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
}
if (enableSchedulingProfiler) {
markRenderStarted(lanes);
}
outer: do {
try {
if (
workInProgressSuspendedReason !== NotSuspended &&
workInProgress !== null
) {
// The work loop is suspended. We need to either unwind the stack or
// replay the suspended component.
// 工作循环已挂起。我们需要要么展开堆栈,要么重新执行挂起的组件。
const unitOfWork = workInProgress;
const thrownValue = workInProgressThrownValue;
resumeOrUnwind: switch (workInProgressSuspendedReason) {
case SuspendedOnError: {
// Unwind then continue with the normal work loop.
// 放松一下,然后继续正常的工作循环。
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(
root,
unitOfWork,
thrownValue,
SuspendedOnError,
);
break;
}
case SuspendedOnData:
case SuspendedOnAction: {
const thenable: Thenable<mixed> = thrownValue as any;
if (isThenableResolved(thenable)) {
// The data resolved. Try rendering the component again.
// 数据已解析。请尝试再次渲染组件。
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
replaySuspendedUnitOfWork(unitOfWork);
break;
}
// The work loop is suspended on data. We should wait for it to
// resolve before continuing to render.
// TODO: Handle the case where the promise resolves synchronously.
// Usually this is handled when we instrument the promise to add a
// `status` field, but if the promise already has a status, we won't
// have added a listener until right here.
//
// 工作循环因数据而暂停。我们应该等待它解决后再继续渲染。
// 待办:处理 promise 同步解决的情况。 通常在我们对 promise 进行检测以添加
// `status` 字段时会处理这种情况,但如果 promise 已经有状态,我们直到这里才会
// 添加监听器。
const onResolution = () => {
// Check if the root is still suspended on this promise.
// 检查根节点是否仍然挂起在这个 promise 上。
if (
(workInProgressSuspendedReason === SuspendedOnData ||
workInProgressSuspendedReason === SuspendedOnAction) &&
workInProgressRoot === root
) {
// Mark the root as ready to continue rendering.
// 将根标记为准备好继续渲染。
workInProgressSuspendedReason = SuspendedAndReadyToContinue;
}
// Ensure the root is scheduled. We should do this even if we're
// currently working on a different root, so that we resume
// rendering later.
//
// 确保根节点已被调度。即使我们当前正在处理不同的根节点,也应该这样做,以便
// 我们稍后可以继续渲染。
ensureRootIsScheduled(root);
};
thenable.then(onResolution, onResolution);
break outer;
}
case SuspendedOnImmediate: {
// If this fiber just suspended, it's possible the data is already
// cached. Yield to the main thread to give it a chance to ping. If
// it does, we can retry immediately without unwinding the stack.
//
// 如果这个 fiber 只是挂起,有可能数据已经被缓存了。让出主线程,给它一个机会
// 去 ping。如果它成功了,我们可以立即重试而不必展开调用栈。
workInProgressSuspendedReason = SuspendedAndReadyToContinue;
break outer;
}
case SuspendedOnInstance: {
workInProgressSuspendedReason =
SuspendedOnInstanceAndReadyToContinue;
break outer;
}
case SuspendedAndReadyToContinue: {
const thenable: Thenable<mixed> = thrownValue as any;
if (isThenableResolved(thenable)) {
// The data resolved. Try rendering the component again.
// 数据已解析。请尝试再次渲染组件。
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
replaySuspendedUnitOfWork(unitOfWork);
} else {
// Otherwise, unwind then continue with the normal work loop.
// 否则,先放松一下,然后继续正常的工作循环。
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(
root,
unitOfWork,
thrownValue,
SuspendedAndReadyToContinue,
);
}
break;
}
case SuspendedOnInstanceAndReadyToContinue: {
let resource: null | Resource = null;
switch (workInProgress.tag) {
case HostHoistable: {
resource = workInProgress.memoizedState;
}
// intentional fallthrough
// 有意贯穿
case HostComponent:
case HostSingleton: {
// Before unwinding the stack, check one more time if the
// instance is ready. It may have loaded when React yielded to
// the main thread.
//
// 在展开堆栈之前,再次检查实例是否已准备好。它可能在 React 让出给主线程时已加载。
// Assigning this to a constant so Flow knows the binding won't
// be mutated by `preloadInstance`.
//
// 将其分配给一个常量,这样 Flow 就知道此绑定不会被 `preloadInstance` 修改。
const hostFiber = workInProgress;
const type = hostFiber.type;
const props = hostFiber.pendingProps;
const isReady = resource
? preloadResource(resource)
: preloadInstance(hostFiber.stateNode, type, props);
if (isReady) {
// The data resolved. Resume the work loop as if nothing
// suspended. Unlike when a user component suspends, we don't
// have to replay anything because the host fiber
// already completed.
//
// 数据已解析。恢复工作循环,好像没有暂停一样。与用户组件挂起时不同,我们
// 不需要重放任何内容,因为宿主 fiber 已经完成了。
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
const sibling = hostFiber.sibling;
if (sibling !== null) {
workInProgress = sibling;
} else {
const returnFiber = hostFiber.return;
if (returnFiber !== null) {
workInProgress = returnFiber;
completeUnitOfWork(returnFiber);
} else {
workInProgress = null;
}
}
break resumeOrUnwind;
}
break;
}
default: {
// This will fail gracefully but it's not correct, so log a
// warning in dev.
// 这将会优雅失败,但这并不正确,因此在开发环境中记录一个警告。
if (__DEV__) {
console.error(
'Unexpected type of fiber triggered a suspensey commit. ' +
'This is a bug in React.',
);
}
break;
}
}
// Otherwise, unwind then continue with the normal work loop.
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(
root,
unitOfWork,
thrownValue,
SuspendedOnInstanceAndReadyToContinue,
);
break;
}
case SuspendedOnDeprecatedThrowPromise: {
// Suspended by an old implementation that uses the `throw promise`
// pattern. The newer replaying behavior can cause subtle issues
// like infinite ping loops. So we maintain the old behavior and
// always unwind.
//
// 由于旧实现使用 `throw promise` 模式而被暂停。新的重放行为可能会引发微妙的
// 问题,比如无限 ping 循环。因此我们保持旧的行为,并始终进行展开。
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
throwAndUnwindWorkLoop(
root,
unitOfWork,
thrownValue,
SuspendedOnDeprecatedThrowPromise,
);
break;
}
case SuspendedOnHydration: {
// Selective hydration. An update flowed into a dehydrated tree.
// Interrupt the current render so the work loop can switch to the
// hydration lane.
//
// 选择性重新渲染。更新流入了一个脱水的树。中断当前渲染,以便工作循环可以切换到重新渲染路径。
resetWorkInProgressStack();
workInProgressRootExitStatus = RootSuspendedAtTheShell;
break outer;
}
default: {
throw new Error(
'Unexpected SuspendedReason. This is a bug in React.',
);
}
}
}
if (__DEV__ && ReactSharedInternals.actQueue !== null) {
// `act` special case: If we're inside an `act` scope, don't consult
// `shouldYield`. Always keep working until the render is complete.
// This is not just an optimization: in a unit test environment, we
// can't trust the result of `shouldYield`, because the host I/O is
// likely mocked.
//
// `act` 特殊情况:如果我们处于 `act` 范围内,不要检查 `shouldYield`。始终
// 继续工作直到渲染完成。这不仅仅是优化:在单元测试环境中,我们无法信任
// `shouldYield` 的结果,因为宿主 I/O 很可能被模拟。
workLoopSync();
} else if (enableThrottledScheduling) {
workLoopConcurrent(includesNonIdleWork(lanes));
} else {
workLoopConcurrentByScheduler();
}
break;
} catch (thrownValue) {
handleThrow(root, thrownValue);
}
} while (true);
resetContextDependencies();
popDispatcher(prevDispatcher);
popAsyncDispatcher(prevAsyncDispatcher);
executionContext = prevExecutionContext;
// Check if the tree has completed.
// 检查树是否已经完成。
if (workInProgress !== null) {
// Still work remaining.
// 仍有工作未完成。
if (enableSchedulingProfiler) {
markRenderYielded();
}
return RootInProgress;
} else {
// Completed the tree.
// 完成了树。
if (enableSchedulingProfiler) {
markRenderStopped();
}
// Set this to null to indicate there's no in-progress render.
// 将此设置为 null 表示没有正在进行的渲染。
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
// It's safe to process the queue now that the render phase is complete.
// 现在渲染阶段已完成,可以安全地处理队列了。
finishQueueingConcurrentUpdates();
// Return the final exit status.
// 返回最终退出状态。
return workInProgressRootExitStatus;
}
}
23. 工作循环并发
/** @noinline */
function workLoopConcurrent(nonIdle: boolean) {
// We yield every other "frame" when rendering Transition or Retries. Those are blocking
// revealing new content. The purpose of this yield is not to avoid the overhead of yielding,
// which is very low, but rather to intentionally block any frequently occuring other main
// thread work like animations from starving our work. In other words, the purpose of this
// is to reduce the framerate of animations to 30 frames per second.
// For Idle work we yield every 5ms to keep animations going smooth.
//
// 在渲染 Transition 或 Retries 时,我们会每隔一帧让出一次。这些是阻塞操作,会显示新内容。让出的目的不是
// 为了避免让出操作的开销(开销非常低),而是为了有意阻止频繁发生的其他主线程工作(如动画)饿死我们的工作。换
// 句话说,其目的是将动画的帧率降低到每秒 30 帧。对于空闲工作,我们每 5 毫秒让出一次,以保持动画流畅。
if (workInProgress !== null) {
const yieldAfter = now() + (nonIdle ? 25 : 5);
do {
performUnitOfWork(workInProgress);
} while (workInProgress !== null && now() < yieldAfter);
}
}
24. 按调度器并发工作循环
备注
shouldYield()由 Scheduler#shouldYield 实现
/** @noinline */
function workLoopConcurrentByScheduler() {
// Perform work until Scheduler asks us to yield
// 持续工作直到调度器要求我们让出
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
25. 执行工作单元
备注
startProfilerTimer()由 ReactProfilerTimer#startProfilerTimer 实现runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现beginWork()由 ReactFiberBeginWork#beginWork 实现stopProfilerTimerIfRunningAndRecordDuration()由 ReactProfilerTimer#stopProfilerTimerIfRunningAndRecordIncompleteDuration 实现
function performUnitOfWork(unitOfWork: Fiber): void {
// 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.
//
// 这个 fiber 当前刷新后的状态是备用状态。理想情况下不应该依赖它,但在这里依赖它意味着我们
// 不需要在进行中的工作上增加额外的字段。
const current = unitOfWork.alternate;
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
if (__DEV__) {
next = runWithFiberInDEV(
unitOfWork,
beginWork,
current,
unitOfWork,
entangledRenderLanes,
);
} else {
next = beginWork(current, unitOfWork, entangledRenderLanes);
}
stopProfilerTimerIfRunningAndRecordDuration(unitOfWork);
} else {
if (__DEV__) {
next = runWithFiberInDEV(
unitOfWork,
beginWork,
current,
unitOfWork,
entangledRenderLanes,
);
} else {
next = beginWork(current, unitOfWork, entangledRenderLanes);
}
}
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
// 如果这没有生成新的工作,请完成当前工作。
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
26. 重放已挂起的工作单元
备注
runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现
function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
// This is a fork of performUnitOfWork specifcally for replaying a fiber that
// just suspended.
// 这是 performUnitOfWork 的一个分支,专门用于重新执行刚刚被挂起的 fiber。
let next;
if (__DEV__) {
next = runWithFiberInDEV(unitOfWork, replayBeginWork, unitOfWork);
} else {
next = replayBeginWork(unitOfWork);
}
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
// 如果这没有生成新的工作,请完成当前工作。
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
}
27. 重新开始工作
备注
startProfilerTimer()由 ReactProfilerTimer#startProfilerTimer 实现getUnmaskedContext()由 ReactFiberLegacyContext#getUnmaskedContext 实现replayFunctionComponent()由 ReactFiberBeginWork#replayFunctionComponent 实现resetHooksOnUnwind()由 ReactFiberHooks#resetHooksOnUnwind 实现unwindInterruptedWork()由 ReactFiberUnwindWork#unwindInterruptedWork 实现resetWorkInProgress()由 ReactFiber 提供beginWork()由 ReactFiberBeginWork#beginWork 实现stopProfilerTimerIfRunningAndRecordDuration()由 ReactProfilerTimer#stopProfilerTimerIfRunningAndRecordDuration 实现
function replayBeginWork(unitOfWork: Fiber): null | Fiber {
// This is a fork of beginWork specifcally for replaying a fiber that
// just suspended.
// 这是 beginWork 的一个分支,专门用于重新执行刚刚挂起的 fiber。
const current = unitOfWork.alternate;
let next;
const isProfilingMode =
enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode;
if (isProfilingMode) {
startProfilerTimer(unitOfWork);
}
switch (unitOfWork.tag) {
case SimpleMemoComponent:
case FunctionComponent: {
// Resolve `defaultProps`. This logic is copied from `beginWork`.
// TODO: Consider moving this switch statement into that module. Also,
// could maybe use this as an opportunity to say `use` doesn't work with
// `defaultProps` :)
//
// 解析 `defaultProps`。此逻辑是从 `beginWork` 复制过来的。
// TODO: 考虑将这个 switch 语句移到那个模块中。此外,也许可以借此机会说明 `use` 不
// 适用于 `defaultProps` :)
const Component = unitOfWork.type;
let context: any;
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(unitOfWork, Component, true);
context = getMaskedContext(unitOfWork, unmaskedContext);
}
next = replayFunctionComponent(
current,
unitOfWork,
unitOfWork.pendingProps,
Component,
context,
workInProgressRootRenderLanes,
);
break;
}
case ForwardRef: {
// Resolve `defaultProps`. This logic is copied from `beginWork`.
// TODO: Consider moving this switch statement into that module. Also,
// could maybe use this as an opportunity to say `use` doesn't work with
// `defaultProps` :)
//
// 解析 `defaultProps`。此逻辑是从 `beginWork` 复制过来的。
// TODO: 考虑将这个 switch 语句移到那个模块中。此外, 也许可以借此机会说明 `use`
// 不适用于 `defaultProps` :)
const Component = unitOfWork.type.render;
next = replayFunctionComponent(
current,
unitOfWork,
unitOfWork.pendingProps,
Component,
unitOfWork.ref,
workInProgressRootRenderLanes,
);
break;
}
case HostComponent: {
// Some host components are stateful (that's how we implement form
// actions) but we don't bother to reuse the memoized state because it's
// not worth the extra code. The main reason to reuse the previous hooks
// is to reuse uncached promises, but we happen to know that the only
// promises that a host component might suspend on are definitely cached
// because they are controlled by us. So don't bother.
//
// 一些宿主组件是有状态的(这就是我们实现表单操作的方式),但我们不去重复使用已缓存的状态,
// 因为增加额外代码不值得。重用之前的 hooks 的主要原因是重用未缓存的 Promise,但我们很
// 清楚宿主组件可能暂停的唯一 Promise 一定是已缓存的,因为它们由我们控制。所以不用费心。
resetHooksOnUnwind(unitOfWork);
// Fallthrough to the next branch.
// 继续执行到下一个分支。
}
default: {
// Other types besides function components are reset completely before
// being replayed. Currently this only happens when a Usable type is
// reconciled — the reconciler will suspend.
//
// 除了函数组件以外的其他类型在重新回放之前会被完全重置。
// 目前这种情况只会在可用类型(Usable type)被协调时发生 — 调和器会挂起。
//
// We reset the fiber back to its original state; however, this isn't
// a full "unwind" because we're going to reuse the promises that were
// reconciled previously. So it's intentional that we don't call
// resetSuspendedWorkLoopOnUnwind here.
//
// 我们将 fiber 重置回其原始状态;然而,这并不是完整的“展开”,因为我们将重用之前已经
// 协调的 promises。所以这里不调用 resetSuspendedWorkLoopOnUnwind 是有意为之的。
unwindInterruptedWork(current, unitOfWork, workInProgressRootRenderLanes);
unitOfWork = workInProgress = resetWorkInProgress(
unitOfWork,
entangledRenderLanes,
);
next = beginWork(current, unitOfWork, entangledRenderLanes);
break;
}
}
if (isProfilingMode) {
stopProfilerTimerIfRunningAndRecordDuration(unitOfWork);
}
return next;
}
28. 抛出并展开工作循环
备注
throwException()由 ReactFiberThrow#throwException 实现getIsHydrating()由 ReactFiberHydrationContext#getIsHydrating 实现includesSomeLane()由 ReactFiberLane#includesSomeLane 实现getSuspenseHandler()由 ReactFiberSuspenseContext#getSuspenseHandler 实现
function throwAndUnwindWorkLoop(
root: FiberRoot,
unitOfWork: Fiber,
thrownValue: mixed,
suspendedReason: SuspendedReason,
) {
// This is a fork of performUnitOfWork specifcally for unwinding a fiber
// that threw an exception.
// 这是 performUnitOfWork 的一个分支,专门用于展开抛出异常的 fiber。
//
// Return to the normal work loop. This will unwind the stack, and potentially
// result in showing a fallback.
// 返回到正常的工作循环。这将展开堆栈,并可能导致显示回退内容。
resetSuspendedWorkLoopOnUnwind(unitOfWork);
const returnFiber = unitOfWork.return;
try {
// Find and mark the nearest Suspense or error boundary that can handle
// this "exception".
// 查找并标记可以处理此“异常”的最近的 Suspense 或错误边界。
const didFatal = throwException(
root,
returnFiber,
unitOfWork,
thrownValue,
workInProgressRootRenderLanes,
);
if (didFatal) {
panicOnRootError(root, thrownValue);
return;
}
} catch (error) {
// We had trouble processing the error. An example of this happening is
// when accessing the `componentDidCatch` property of an error boundary
// throws an error. A weird edge case. There's a regression test for this.
// To prevent an infinite loop, bubble the error up to the next parent.
//
// 我们在处理错误时遇到了问题。发生这种情况的一个例子是当访问错误边界的
// `componentDidCatch` 属性时会抛出一个错误。这是一个奇怪的边缘情况。对此有回归测试。
// 为了防止无限循环,将错误抛给上一级父组件。
if (returnFiber !== null) {
workInProgress = returnFiber;
throw error;
} else {
panicOnRootError(root, thrownValue);
return;
}
}
if (unitOfWork.flags & Incomplete) {
// Unwind the stack until we reach the nearest boundary.
// 展开堆栈,直到到达最近的边界。
let skipSiblings;
if (
// The current algorithm for both hydration and error handling assumes
// that the tree is rendered sequentially. So we always skip the siblings.
// 当前用于水合和错误处理的算法假设树是按顺序渲染的。因此我们总是跳过兄弟节点。
getIsHydrating() ||
suspendedReason === SuspendedOnError
) {
skipSiblings = true;
// We intentionally don't set workInProgressRootDidSkipSuspendedSiblings,
// because we don't want to trigger another prerender attempt.
//
// 我们故意不设置 workInProgressRootDidSkipSuspendedSiblings,
// 因为我们不想触发另一次预渲染尝试。
} else if (
// Check whether this is a prerender
!workInProgressRootIsPrerendering &&
// Offscreen rendering is also a form of speculative rendering
// 离屏渲染也是一种推测性渲染
!includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
) {
// This is not a prerender. Skip the siblings during this render. A
// separate prerender will be scheduled for later.
// 这不是预渲染。在此渲染期间跳过兄弟节点。单独的预渲染将在稍后安排。
skipSiblings = true;
workInProgressRootDidSkipSuspendedSiblings = true;
// Because we're skipping the siblings, schedule an immediate retry of
// this boundary.
//
// 因为我们跳过了兄弟节点,所以立即安排重试此边界。
//
// The reason we do this is because a prerender is only scheduled when
// the root is blocked from committing, i.e. RootSuspendedWithDelay.
// When the root is not blocked, as in the case when we render a
// fallback, the original lane is considered to be finished, and
// therefore no longer in need of being prerendered. However, there's
// still a pending retry that will happen once the data streams in.
// We should start rendering that even before the data streams in so we
// can prerender the siblings.
//
// 我们这样做的原因是,只有在根节点被阻塞提交时才会安排预渲染,也就是
// RootSuspendedWithDelay。当根节点未被阻塞时,例如在渲染回退内容的情况下,原来的
// lane 会被认为已经完成,因此不再需要预渲染。然而,一旦数据流入,仍然会有一个待执行的
// 重试。我们应该在数据流入之前就开始渲染它,这样我们就可以预渲染其兄弟节点。
if (
suspendedReason === SuspendedOnData ||
suspendedReason === SuspendedOnAction ||
suspendedReason === SuspendedOnImmediate ||
suspendedReason === SuspendedOnDeprecatedThrowPromise
) {
const boundary = getSuspenseHandler();
if (boundary !== null && boundary.tag === SuspenseComponent) {
boundary.flags |= ScheduleRetry;
}
}
} else {
// This is a prerender. Don't skip the siblings.
// 这是一个预渲染。不要跳过兄弟节点。
skipSiblings = false;
}
unwindUnitOfWork(unitOfWork, skipSiblings);
} else {
// Although the fiber suspended, we're intentionally going to commit it in
// an inconsistent state. We can do this safely in cases where we know the
// inconsistent tree will be hidden.
//
// 尽管 fiber 被挂起,我们仍打算故意将其置于不一致的状态。在我们知道不一致的树会被隐藏的
// 情况下,这样做是安全的。
//
// This currently only applies to Legacy Suspense implementation, but we may
// port a version of this to concurrent roots, too, when performing a
// synchronous render. Because that will allow us to mutate the tree as we
// go instead of buffering mutations until the end. Though it's unclear if
// this particular path is how that would be implemented.
//
// 这目前仅适用于旧版 Suspense 实现,但我们也可能会将其移植到并发根中,当执行同步渲染时使用。
// 因为这样可以让我们在渲染过程中就修改树,而不是等到最后再进行修改。不过,目前还不清楚这种特定
// 路径是否会被用来实现这一点。
completeUnitOfWork(unitOfWork);
}
}
29. 根错误时触发恐慌
备注
logUncaughtError()由 ReactFiberErrorLogger#logUncaughtError 提供createCapturedValueAtFiber()由 ReactCapturedValue#createCapturedValueAtFiber 提供
function panicOnRootError(root: FiberRoot, error: mixed) {
// There's no ancestor that can handle this exception. This should never
// happen because the root is supposed to capture all errors that weren't
// caught by an error boundary. This is a fatal error, or panic condition,
// because we've run out of ways to recover.
//
// 没有任何上级可以处理此异常。这种情况不应该发生,因为根节点应该捕获所有未被错误边界捕获
// 的错误。这是一个致命错误或恐慌状态,因为我们已经没有办法进行恢复了。
workInProgressRootExitStatus = RootFatalErrored;
logUncaughtError(root, createCapturedValueAtFiber(error, root.current));
// Set `workInProgress` to null. This represents advancing to the next
// sibling, or the parent if there are no siblings. But since the root
// has no siblings nor a parent, we set it to null. Usually this is
// handled by `completeUnitOfWork` or `unwindWork`, but since we're
// intentionally not calling those, we need set it here.
// TODO: Consider calling `unwindWork` to pop the contexts.
//
// 将 `workInProgress` 设置为 null。这表示前进到下一个兄弟节点,如果没有兄弟节点,则
// 前进到父节点。但由于根节点没有兄弟节点也没有父节点,所以我们将其设置为 null。通常这由
// `completeUnitOfWork` 或 `unwindWork` 处理,但由于我们故意不调用它们,所以需要在
// 这里设置。
// TODO: 考虑调用 `unwindWork` 来弹出上下文。
workInProgress = null;
}
30. 完成工作单元
备注
startProfilerTimer()由 ReactProfilerTimer#startProfilerTimer 实现runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现completeWork()由 ReactFiberCompleteWork#completeWork 实现stopProfilerTimerIfRunningAndRecordIncompleteDuration()由 ReactProfilerTimer#stopProfilerTimerIfRunningAndRecordIncompleteDuration 实现
function completeUnitOfWork(unitOfWork: Fiber): void {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
//
// 尝试完成当前的工作单元,然后移动到下一个同级。如果没有更多同级,则返回到父 fiber。
let completedWork: Fiber = unitOfWork;
do {
if ((completedWork.flags & Incomplete) !== NoFlags) {
// This fiber did not complete, because one of its children did not
// complete. Switch to unwinding the stack instead of completing it.
//
// 这个 fiber 没有完成,因为它的一个子节点没有完成。转而回退堆栈,而不是完成它。
//
// The reason "unwind" and "complete" is interleaved is because when
// something suspends, we continue rendering the siblings even though
// they will be replaced by a fallback.
//
// “unwind”和“complete”交错出现的原因是,当某些内容暂停时,我们会继续渲染其兄弟
// 节点,尽管它们最终会被替代为回退内容。
const skipSiblings = workInProgressRootDidSkipSuspendedSiblings;
unwindUnitOfWork(completedWork, skipSiblings);
return;
}
// 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.
//
// 这个 fiber 当前刷新后的状态是备用状态。理想情况下不应该依赖它,但在这里依赖它意味着
// 我们不需要在进行中的工作上添加额外的字段。
const current = completedWork.alternate;
const returnFiber = completedWork.return;
let next;
startProfilerTimer(completedWork);
if (__DEV__) {
next = runWithFiberInDEV(
completedWork,
completeWork,
current,
completedWork,
entangledRenderLanes,
);
} else {
next = completeWork(current, completedWork, entangledRenderLanes);
}
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
// Update render duration assuming we didn't error.
// 假设没有发生错误,更新渲染时长。
stopProfilerTimerIfRunningAndRecordIncompleteDuration(completedWork);
}
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
// 完成这个 fiber 后生成了新的工作。接下来处理那个工作。
workInProgress = next;
return;
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
// 如果在这个 returnFiber 中还有更多工作要做,就接着做。
workInProgress = siblingFiber;
return;
}
// Otherwise, return to the parent
// 否则,返回上级
completedWork = returnFiber;
// Update the next thing we're working on in case something throws.
// 更新我们接下来的工作内容,以防出现异常。
workInProgress = completedWork;
} while (completedWork !== null);
// We've reached the root.
// 我们已经到达根目录。
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}
31. 解除工作单元
备注
unwindWork()由 ReactFiberUnwindWork#unwindWork 实现stopProfilerTimerIfRunningAndRecordIncompleteDuration()由 ReactProfilerTimer#stopProfilerTimerIfRunningAndRecordIncompleteDuration 实现
function unwindUnitOfWork(unitOfWork: Fiber, skipSiblings: boolean): void {
let incompleteWork: Fiber = unitOfWork;
do {
// 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.
//
// 这个 fiber 当前已刷新(flushed)的状态是备用状态(alternate)。理想情况下,任何东西
// 都不应该依赖它,但在这里依赖它意味着我们不需要在正在进行的工作中添加额外的字段。
const current = incompleteWork.alternate;
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
//
// 这个 fiber 没有完成,因为某些东西抛出了异常。从栈中弹出值而没有进入完成阶段。如果
// 这是一个边界,尽可能捕获值。
const next = unwindWork(current, incompleteWork, entangledRenderLanes);
// Because this fiber did not complete, don't reset its lanes.
// 因为这个 fiber 没有完成,所以不要重置它的 lanes。
if (next !== null) {
// Found a boundary that can handle this exception. Re-renter the
// begin phase. This branch will return us to the normal work loop.
// 找到了可以处理此异常的边界。重新进入开始阶段。这条分支会把我们带回正常的工作循环。
//
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
// 由于我们要重新开始,移除 effect 标签中所有不是主机效果的内容。
next.flags &= HostEffectMask;
workInProgress = next;
return;
}
// Keep unwinding until we reach either a boundary or the root.
// 继续展开,直到达到边界或根节点。
if (enableProfilerTimer && (incompleteWork.mode & ProfileMode) !== NoMode) {
// Record the render duration for the fiber that errored.
// 记录发生错误的 fiber 的渲染时长。
stopProfilerTimerIfRunningAndRecordIncompleteDuration(incompleteWork);
// Include the time spent working on failed children before continuing.
// 在继续之前,包括处理失败子项所花费的时间。
let actualDuration = incompleteWork.actualDuration;
let child = incompleteWork.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
incompleteWork.actualDuration = actualDuration;
}
// TODO: Once we stop prerendering siblings, instead of resetting the parent
// of the node being unwound, we should be able to reset node itself as we
// unwind the stack. Saves an additional null check.
//
// 待办事项:一旦我们停止对兄弟节点进行预渲染,我们应该能够在展开堆栈时重置节点本身,而不是
// 重置被展开的节点的父节点。这可以省去额外的空值检查。
const returnFiber = incompleteWork.return;
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its subtree flags.
// TODO: Once we stop prerendering siblings, we may be able to get rid of
// the Incomplete flag because unwinding to the nearest boundary will
// happen synchronously.
//
// 将父 fiber 标记为未完成,并清除其子树标记。
// 待办事项:一旦我们停止预渲染兄弟节点,可能就可以去掉未完成标志,因为回溯到最近的边界
// 将是同步发生的。
returnFiber.flags |= Incomplete;
returnFiber.subtreeFlags = NoFlags;
returnFiber.deletions = null;
}
if (!skipSiblings) {
const siblingFiber = incompleteWork.sibling;
if (siblingFiber !== null) {
// This branch will return us to the normal work loop.
// 这个分支将把我们带回到正常的工作循环。
workInProgress = siblingFiber;
return;
}
}
// Otherwise, return to the parent
// 否则,返回上级
incompleteWork = returnFiber;
// Update the next thing we're working on in case something throws.
// 更新我们接下来要处理的内容,以防出现异常。
workInProgress = incompleteWork;
} while (incompleteWork !== null);
// We've unwound all the way to the root.
// 我们已经回溯到根源。
workInProgressRootExitStatus = RootSuspendedAtTheShell;
workInProgress = null;
}
32. 提交根节点
备注
setCurrentTrackFromLanes()由 ReactFiberPerformanceTrack#setCurrentTrackFromLanes 实现logErroredRenderPhase()由 ReactFiberPerformanceTrack#logErroredRenderPhase 实现logRecoveredRenderPhase()由 ReactFiberPerformanceTrack#logRecoveredRenderPhase 实现logRenderPhase()由 ReactFiberPerformanceTrack#logRenderPhase 实现markCommitStarted()由 ReactFiberDevToolsHook#markCommitStarted 实现markCommitStopped()由 ReactFiberDevToolsHook#markCommitStopped 实现stopCompletedGestures()由 ReactFiberGestureScheduler#stopCompletedGestures 实现mergeLanes()由 ReactFiberLane#mergeLanes 实现getConcurrentlyUpdatedLanes()由 ReactFiberConcurrentUpdates#getConcurrentlyUpdatedLanes 实现clearGestureUpdates()由 ReactProfilerTimer#clearGestureUpdates 实现markRootFinished()由 ReactFiberLane#markRootFinished 实现isGestureRender()由 ReactFiberLane#isGestureRender 实现includesOnlyViewTransitionEligibleLanes()由 ReactFiberLane#includesOnlyViewTransitionEligibleLanes 实现claimQueuedTransitionTypes()由 ReactFiberTransitionTypes#claimQueuedTransitionTypes 实现trackSchedulerEvent()由宿主环境提供resetCommitErrors()由 ReactProfilerTimer#resetCommitErrors 实现recordCommitTime()由 ReactProfilerTimer#recordCommitTime 实现resetShouldStartViewTransition()由 ReactFiberCommitViewTransitions#resetShouldStartViewTransition 实现getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供commitBeforeMutationEffects()由 ReactFiberCommitWork#commitBeforeMutationEffects 实现startAnimating()由 ReactProfilerTimer#startAnimating 实现startViewTransition()由宿主环境提供
function commitRoot(
root: FiberRoot,
finishedWork: null | Fiber,
lanes: Lanes,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
didIncludeRenderPhaseUpdate: boolean,
spawnedLane: Lane,
updatedLanes: Lanes,
suspendedRetryLanes: Lanes,
exitStatus: RootExitStatus,
suspendedState: null | SuspendedState,
// 仅供性能分析用
suspendedCommitReason: SuspendedCommitReason, // Profiling-only
// 仅供性能分析用
completedRenderStartTime: number, // Profiling-only
// 仅供性能分析用
completedRenderEndTime: number, // Profiling-only
): void {
root.cancelPendingCommit = null;
do {
// `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
// means `flushPassiveEffects` will sometimes result in additional
// passive effects. So we need to keep flushing in a loop until there are
// no more pending effects.
// TODO: Might be better if `flushPassiveEffects` did not automatically
// flush synchronous work at the end, to avoid factoring hazards like this.
//
// `flushPassiveEffects` 会在最后调用 `flushSyncUpdateQueue`,这意味着
// `flushPassiveEffects` 有时会导致额外的被动副作用。因此我们需要在循环中持续刷新,直到
// 没有更多待处理的副作用。
// TODO: 如果 `flushPassiveEffects` 在最后不自动刷新同步工作,可能会更好,以避免像这样的
// 分类风险。
flushPendingEffects();
} while (pendingEffectsStatus !== NO_PENDING_EFFECTS);
flushRenderPhaseStrictModeWarningsInDEV();
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// Log the previous render phase once we commit. I.e. we weren't interrupted.
// 一旦提交,就记录之前的渲染阶段。也就是说,我们没有被中断。
setCurrentTrackFromLanes(lanes);
if (exitStatus === RootErrored) {
logErroredRenderPhase(
completedRenderStartTime,
completedRenderEndTime,
lanes,
workInProgressUpdateTask,
);
} else if (recoverableErrors !== null) {
const hydrationFailed =
finishedWork !== null &&
finishedWork.alternate !== null &&
(finishedWork.alternate.memoizedState as RootState).isDehydrated &&
(finishedWork.flags & ForceClientRender) !== NoFlags;
logRecoveredRenderPhase(
completedRenderStartTime,
completedRenderEndTime,
lanes,
recoverableErrors,
hydrationFailed,
workInProgressUpdateTask,
);
} else {
logRenderPhase(
completedRenderStartTime,
completedRenderEndTime,
lanes,
workInProgressUpdateTask,
);
}
}
if (enableSchedulingProfiler) {
markCommitStarted(lanes);
}
if (finishedWork === null) {
if (enableSchedulingProfiler) {
markCommitStopped();
}
if (enableGestureTransition) {
// Stop any gestures that were completed and is now being reverted.
// 停止任何已完成但现在正在回退的手势。
if (root.stoppingGestures !== null) {
stopCompletedGestures(root);
}
}
return;
} else {
if (__DEV__) {
if (lanes === NoLanes) {
console.error(
'finishedLanes should not be empty during a commit. This is a ' +
'bug in React.',
);
}
}
}
if (finishedWork === root.current) {
throw new Error(
'Cannot commit the same tree as before. This error is likely caused by ' +
'a bug in React. Please file an issue.',
);
}
// Check which lanes no longer have any work scheduled on them, and mark
// those as finished.
// 检查哪些通道不再有任何计划的工作,并将这些通道标记为已完成。
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
// Make sure to account for lanes that were updated by a concurrent event
// during the render phase; don't mark them as finished.
// 确保考虑到在渲染阶段被并发事件更新的车道;不要将它们标记为已完成。
const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
if (enableGestureTransition && root.pendingGestures === null) {
// Gestures don't clear their lanes while the gesture is still active but it
// might not be scheduled to do any more renders and so we shouldn't schedule
// any more gesture lane work until a new gesture is scheduled.
//
// 手势在仍然处于活动状态时不会清除它们的通道,但它可能没有被安排进行更多的渲染,因此我们不应安排
// 任何更多的手势通道工作,直到安排了新的手势。
if (enableProfilerTimer && (remainingLanes & GestureLane) !== NoLanes) {
// We need to clear any updates scheduled so that we can treat future updates
// as the cause of the render.
// 我们需要清除所有计划的更新,以便我们可以将未来的更新视为渲染的原因。
clearGestureUpdates();
}
remainingLanes &= ~GestureLane;
}
markRootFinished(
root,
lanes,
remainingLanes,
spawnedLane,
updatedLanes,
suspendedRetryLanes,
);
// Reset this before firing side effects so we can detect recursive updates.
// 在触发副作用之前重置它,以便我们可以检测到递归更新。
didIncludeCommitPhaseUpdate = false;
if (root === workInProgressRoot) {
// We can reset these now that they are finished.
// 现在它们完成了,我们可以重置它们。
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
} else {
// This indicates that the last root we worked on is not the same one that
// we're committing now. This most commonly happens when a suspended root
// times out.
// 这表示我们上一次处理的根节点与我们现在提交的根节点不同。最常见的情况是挂起的根节点超时。
}
// workInProgressX might be overwritten, so we want
// to store it in pendingPassiveX until they get processed
// We need to pass this through as an argument to commitRoot
// because workInProgressX might have changed between
// the previous render and commit if we throttle the commit
// with setTimeout
//
// workInProgressX 可能会被覆盖,所以我们要把它存储在 pendingPassiveX 中,直到它们被
// 处理。我们需要将它作为参数传递给 commitRoot,因为如果我们用 setTimeout 节流 commit,
// workInProgressX 可能在之前的渲染和提交之间发生了变化
pendingFinishedWork = finishedWork;
pendingEffectsRoot = root;
pendingEffectsLanes = lanes;
pendingEffectsRemainingLanes = remainingLanes;
pendingPassiveTransitions = transitions;
pendingRecoverableErrors = recoverableErrors;
pendingDidIncludeRenderPhaseUpdate = didIncludeRenderPhaseUpdate;
if (enableProfilerTimer) {
pendingEffectsRenderEndTime = completedRenderEndTime;
pendingSuspendedCommitReason = suspendedCommitReason;
pendingDelayedCommitReason = IMMEDIATE_COMMIT;
pendingSuspendedViewTransitionReason = null;
}
if (enableGestureTransition && isGestureRender(lanes)) {
// This is a special kind of render that doesn't commit regular effects.
// 这是一种不会提交常规效果的特殊渲染。
commitGestureOnRoot(
root,
finishedWork,
recoverableErrors,
suspendedState,
enableProfilerTimer
? suspendedCommitReason === null
? completedRenderEndTime
: commitStartTime
: 0,
);
return;
}
// If there are pending passive effects, schedule a callback to process them.
// Do this as early as possible, so it is queued before anything else that
// might get scheduled in the commit phase. (See #16714.)
// TODO: Delete all other places that schedule the passive effect callback
// They're redundant.
//
// 如果有待处理的被动效果,安排一个回调来处理它们。尽早执行此操作,以便在提交阶段可能安排的其他
// 任何事情之前排队。(参见 #16714。)
// TODO:删除所有其他安排被动效果回调的地方
// 它们是多余的。
let passiveSubtreeMask;
if (enableViewTransition) {
pendingViewTransitionEvents = null;
if (includesOnlyViewTransitionEligibleLanes(lanes)) {
// Claim any pending Transition Types for this commit.
// 为此提交声明任何待处理的转换类型。
pendingTransitionTypes = claimQueuedTransitionTypes(root);
passiveSubtreeMask = PassiveTransitionMask;
} else {
pendingTransitionTypes = null;
passiveSubtreeMask = PassiveMask;
}
} else {
passiveSubtreeMask = PassiveMask;
}
if (
// If this subtree rendered with profiling this commit, we need to visit it to log it.
// 如果这个子树使用剖析渲染了此提交,我们需要访问它以进行记录。
(enableProfilerTimer &&
enableComponentPerformanceTrack &&
finishedWork.actualDuration !== 0) ||
(finishedWork.subtreeFlags & passiveSubtreeMask) !== NoFlags ||
(finishedWork.flags & passiveSubtreeMask) !== NoFlags
) {
if (enableYieldingBeforePassive) {
// We don't schedule a separate task for flushing passive effects.
// Instead, we just rely on ensureRootIsScheduled below to schedule
// a callback for us to flush the passive effects.
//
// 我们不会为刷新被动副作用安排单独的任务。相反,我们只是依赖下面的
// ensureRootIsScheduled 来为我们安排一个回调以刷新被动副作用。
} else {
// So we can clear these now to allow a new callback to be scheduled.
// 所以我们现在可以清除这些,以便允许安排新的回调。
root.callbackNode = null;
root.callbackPriority = NoLane;
scheduleCallback(NormalSchedulerPriority, () => {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// Track the currently executing event if there is one so we can ignore this
// event when logging events.
//
// 跟踪当前正在执行的事件(如果有),以便在记录事件时可以忽略此事件。
trackSchedulerEvent();
}
if (pendingDelayedCommitReason === IMMEDIATE_COMMIT) {
pendingDelayedCommitReason = DELAYED_PASSIVE_COMMIT;
}
flushPassiveEffects();
// This render triggered passive effects: release the root cache pool
// *after* passive effects fire to avoid freeing a cache pool that may
// be referenced by a node in the tree (HostRoot, Cache boundary etc)
//
// 此渲染触发了被动副作用:释放根缓存池。在被动副作用执行之后释放,以避免释放可能被树中的
// 节点引用的缓存池(HostRoot、缓存边界等)
return null;
});
}
} else {
// If we don't have passive effects, we're not going to need to perform more work
// so we can clear the callback now.
//
// 如果我们没有被动副作用,我们就不需要执行更多工作,所以我们现在可以清除回调。
root.callbackNode = null;
root.callbackPriority = NoLane;
}
if (enableProfilerTimer) {
// Mark the current commit time to be shared by all Profilers in this
// batch. This enables them to be grouped later.
//
// 标记当前提交时间以便此批次中的所有分析器共享。这使它们以后可以被分组。
resetCommitErrors();
recordCommitTime();
if (enableComponentPerformanceTrack) {
if (suspendedCommitReason !== null) {
logSuspendedCommitPhase(
completedRenderEndTime,
commitStartTime,
suspendedCommitReason,
workInProgressUpdateTask,
);
}
}
}
resetShouldStartViewTransition();
// The commit phase is broken into several sub-phases. We do a separate pass
// of the effect list for each phase: all mutation effects come before all
// layout effects, and so on.
//
// 提交阶段被分成几个子阶段。我们为每个阶段单独遍历一次效果列表:所有的变更效果都在所有布局
// 效果之前,依此类推。
// Check if there are any effects in the whole tree.
// TODO: This is left over from the effect list implementation, where we had
// to check for the existence of `firstEffect` to satisfy Flow. I think the
// only other reason this optimization exists is because it affects profiling.
// Reconsider whether this is necessary.
//
// 检查整个树中是否存在任何副作用。
// TODO: 这是从 effect 列表的实现中遗留下来的,当时我们必须检查 `firstEffect` 的存在以满足
// Flow。我认为这个优化存在的唯一其他原因是它会影响性能分析。重新考虑这是否有必要。
const subtreeHasBeforeMutationEffects =
(finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask)) !==
NoFlags;
const rootHasBeforeMutationEffect =
(finishedWork.flags & (BeforeMutationMask | MutationMask)) !== NoFlags;
if (subtreeHasBeforeMutationEffects || rootHasBeforeMutationEffect) {
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
try {
// The first phase a "before mutation" phase. We use this phase to read the
// state of the host tree right before we mutate it. This is where
// getSnapshotBeforeUpdate is called.
//
// 第一阶段是“变更前”阶段。我们在这个阶段读取宿主树的状态,就在我们对其进行变更之前。
// 这里会调用 getSnapshotBeforeUpdate。
commitBeforeMutationEffects(root, finishedWork, lanes);
} finally {
// Reset the priority to the previous non-sync value.
// 将优先级重置为之前的非同步值。
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}
let willStartViewTransition = shouldStartViewTransition;
if (enableGestureTransition) {
// Stop any gestures that were completed and is now being committed.
// 停止任何已完成并正在提交的手势。
if (root.stoppingGestures !== null) {
stopCompletedGestures(root);
// If we are in the process of stopping some gesture we shouldn't start
// a View Transition because that would start from the previous state to
// the next state.
//
// 如果我们正在停止某个手势的过程中,我们不应该开始一个视图过渡,
// 因为那会从上一个状态过渡到下一个状态。
willStartViewTransition = false;
}
}
pendingEffectsStatus = PENDING_MUTATION_PHASE;
if (enableViewTransition && willStartViewTransition) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
startAnimating(lanes);
}
pendingViewTransition = startViewTransition(
suspendedState,
root.containerInfo,
pendingTransitionTypes,
flushMutationEffects,
flushLayoutEffects,
flushAfterMutationEffects,
flushSpawnedWork,
flushPassiveEffects,
reportViewTransitionError,
enableProfilerTimer ? suspendedViewTransition : (null as any),
enableProfilerTimer
? // This callback fires after "pendingEffects" so we need to snapshot the arguments.
// 这个回调在“pendingEffects”之后触发,所以我们需要快照参数。
finishedViewTransition.bind(null, lanes)
: (null as any),
);
} else {
// Flush synchronously.
// 同步刷新。
flushMutationEffects();
flushLayoutEffects();
// Skip flushAfterMutationEffects
// 在变更效果之后跳过刷新
flushSpawnedWork();
}
}
33.报告视图转换错误
function reportViewTransitionError(error: mixed) {
// Report errors that happens while preparing a View Transition.
// 报告在准备视图切换时发生的错误。
if (pendingEffectsStatus === NO_PENDING_EFFECTS) {
return;
}
const root = pendingEffectsRoot;
const onRecoverableError = root.onRecoverableError;
onRecoverableError(error, makeErrorInfo(null));
}
34.挂起的视图过渡
备注
recordCommitEndTime()由 ReactProfilerTimer#recordCommitEndTime 实现logCommitPhase()由 ReactFiberPerformanceTrack#logCommitPhase 实现
function suspendedViewTransition(reason: string): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// We'll split the commit into two phases, because we're suspended in the middle.
// 我们会将提交分为两个阶段,因为我们在中途被暂停。
recordCommitEndTime();
logCommitPhase(
pendingSuspendedCommitReason === null
? pendingEffectsRenderEndTime
: commitStartTime,
commitEndTime,
commitErrors,
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
// TODO: Use a ViewTransition Task and this is not safe to read in this phase.
// 待办:使用 ViewTransition 任务,在此阶段读取不安全。
workInProgressUpdateTask,
);
pendingSuspendedViewTransitionReason = reason;
pendingSuspendedCommitReason = reason;
}
}
35. 完成视图过渡
备注
stopAnimating()由 ReactProfilerTimer#stopAnimating 实现isGestureRender()由 ReactFiberLane#isGestureRender 实现setCurrentTrackFromLanes()由 ReactFiberPerformanceTrack#setCurrentTrackFromLanes 实现logAnimatingPhase()由 ReactFiberPerformanceTrack#logAnimatingPhase 实现includesTransitionLane()由 ReactFiberLane#includesTransitionLane 实现includesRetryLane()由 ReactFiberLane#includesRetryLane 实现includesIdleGroupLanes()由 ReactFiberLane#includesIdleGroupLanes 实现now()由 Scheduler#now 实现
function finishedViewTransition(lanes: Lanes): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if ((animatingLanes & lanes) === NoLanes) {
// Was already stopped by some other action or maybe other root.
// 已经被其他操作或可能是其他根节点停止。
return;
}
const task = animatingTask;
stopAnimating(lanes);
// If an affected track isn't in the middle of rendering or committing, log from the previous
// finished render until the end of the animation.
// 如果受影响的轨道不在渲染或提交中间,从上一次完成的渲染记录到动画结束。
if (
isGestureRender(lanes) &&
!isGestureRender(workInProgressRootRenderLanes) &&
!isGestureRender(pendingEffectsLanes)
) {
setCurrentTrackFromLanes(GestureLane);
logAnimatingPhase(gestureClampTime, now(), task);
}
if (
includesTransitionLane(lanes) &&
!includesTransitionLane(workInProgressRootRenderLanes) &&
!includesTransitionLane(pendingEffectsLanes)
) {
setCurrentTrackFromLanes(SomeTransitionLane);
logAnimatingPhase(transitionClampTime, now(), task);
}
if (
includesRetryLane(lanes) &&
!includesRetryLane(workInProgressRootRenderLanes) &&
!includesRetryLane(pendingEffectsLanes)
) {
setCurrentTrackFromLanes(SomeRetryLane);
logAnimatingPhase(retryClampTime, now(), task);
}
if (
includesIdleGroupLanes(lanes) &&
!includesIdleGroupLanes(workInProgressRootRenderLanes) &&
!includesIdleGroupLanes(pendingEffectsLanes)
) {
setCurrentTrackFromLanes(IdleLane);
logAnimatingPhase(idleClampTime, now(), task);
}
}
}
36. 在变更副作用后刷新
备注
commitAfterMutationEffects()由 ReactFiberCommitWork#commitAfterMutationEffects 实现
function flushAfterMutationEffects(): void {
if (pendingEffectsStatus !== PENDING_AFTER_MUTATION_PHASE) {
return;
}
pendingEffectsStatus = NO_PENDING_EFFECTS;
const root = pendingEffectsRoot;
const finishedWork = pendingFinishedWork;
const lanes = pendingEffectsLanes;
commitAfterMutationEffects(root, finishedWork, lanes);
pendingEffectsStatus = PENDING_SPAWNED_WORK;
}
37. 刷新突变效果
备注
getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供commitMutationEffects()由 ReactFiberCommitWork#commitMutationEffects 实现afterActiveInstanceBlur()由宿主环境提供resetAfterCommit()由宿主环境提供
function flushMutationEffects(): void {
if (pendingEffectsStatus !== PENDING_MUTATION_PHASE) {
return;
}
pendingEffectsStatus = NO_PENDING_EFFECTS;
const root = pendingEffectsRoot;
const finishedWork = pendingFinishedWork;
const lanes = pendingEffectsLanes;
const subtreeMutationHasEffects =
(finishedWork.subtreeFlags & MutationMask) !== NoFlags;
const rootMutationHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
if (subtreeMutationHasEffects || rootMutationHasEffect) {
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
try {
// The next phase is the mutation phase, where we mutate the host tree.
// 下一阶段是变异阶段,我们会对宿主树进行变异。
commitMutationEffects(root, finishedWork, lanes);
if (enableCreateEventHandleAPI) {
if (shouldFireAfterActiveInstanceBlur) {
afterActiveInstanceBlur();
}
}
resetAfterCommit(root.containerInfo);
} finally {
// Reset the priority to the previous non-sync value.
// 将优先级重置为之前的非同步值。
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}
// The work-in-progress tree is now the current tree. This must come after
// the mutation phase, so that the previous tree is still current during
// componentWillUnmount, but before the layout phase, so that the finished
// work is current during componentDidMount/Update.
//
// 正在进行的树现在是当前树。这必须在变更阶段之后进行,这样在 componentWillUnmount 期间
// 前一个树仍然是当前树,但在布局阶段之前完成,这样在 componentDidMount/Update 期间完成
// 的工作就是当前的。
root.current = finishedWork;
pendingEffectsStatus = PENDING_LAYOUT_PHASE;
}
38. 刷新布局效果
备注
recordCommitTime()由 ReactProfilerTimer#recordCommitTime 实现logSuspendedViewTransitionPhase()由 ReactFiberPerformanceTrack#logSuspendedViewTransitionPhase 实现getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供reportGlobalError()由宿主环境提供markLayoutEffectsStarted()由 ReactFiberDevToolsHook#markLayoutEffectsStarted 实现commitLayoutEffects()由 ReactFiberCommitWork#commitLayoutEffects 实现markLayoutEffectsStopped()由 ReactFiberDevToolsHook#markLayoutEffectsStopped 实现recordCommitEndTime()由 ReactProfilerTimer#recordCommitTime 实现logCommitPhase()由 ReactFiberPerformanceTrack#logCommitPhase 实现
function flushLayoutEffects(): void {
if (pendingEffectsStatus !== PENDING_LAYOUT_PHASE) {
return;
}
pendingEffectsStatus = NO_PENDING_EFFECTS;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
const suspendedViewTransitionReason = pendingSuspendedViewTransitionReason;
if (suspendedViewTransitionReason !== null) {
// We suspended in the middle of the commit for the view transition.
// We'll start a new commit track now.
// 我们在视图切换的提交过程中暂停了。我们现在将开始一个新的提交轨迹。
recordCommitTime();
logSuspendedViewTransitionPhase(
// 开始就是第一次提交部分的结束。
commitEndTime, // The start is the end of the first commit part.
// 结束是第二次提交部分的开始。
commitStartTime, // The end is the start of the second commit part.
suspendedViewTransitionReason,
animatingTask,
);
}
}
const root = pendingEffectsRoot;
const finishedWork = pendingFinishedWork;
const lanes = pendingEffectsLanes;
if (enableDefaultTransitionIndicator) {
const cleanUpIndicator = root.pendingIndicator;
if (cleanUpIndicator !== null && root.indicatorLanes === NoLanes) {
// We have now committed all Transitions that needed the default indicator
// so we can now run the clean up function. We do this in the layout phase
// so it has the same semantics as if you did it with a useLayoutEffect or
// if it was reset automatically with useOptimistic.
//
// 我们现在已经提交了所有需要默认指示器的过渡,所以我们现在可以运行清理函数。我们在布局阶
// 段执行此操作。因此它的语义与使用 useLayoutEffect 或使用 useOptimistic 自动重置时
// 的效果相同。
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
root.pendingIndicator = null;
try {
cleanUpIndicator();
} catch (x) {
reportGlobalError(x);
} finally {
// Reset the priority to the previous non-sync value.
// 将优先级重置为之前的非同步值。
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}
}
const subtreeHasLayoutEffects =
(finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
const rootHasLayoutEffect = (finishedWork.flags & LayoutMask) !== NoFlags;
if (subtreeHasLayoutEffects || rootHasLayoutEffect) {
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
try {
// The next phase is the layout phase, where we call effects that read
// the host tree after it's been mutated. The idiomatic use case for this is
// layout, but class component lifecycles also fire here for legacy reasons.
//
// 下一阶段是布局阶段,在这个阶段我们会调用读取主机树(host tree)的副作用,前提是主机树已经
// 被修改过了。最常见的用途是布局,但出于兼容旧代码的原因,类组件的生命周期方法也会在此阶段触发。
if (enableSchedulingProfiler) {
markLayoutEffectsStarted(lanes);
}
commitLayoutEffects(finishedWork, root, lanes);
if (enableSchedulingProfiler) {
markLayoutEffectsStopped();
}
} finally {
// Reset the priority to the previous non-sync value.
// 将优先级重置为之前的非同步值。
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
}
const completedRenderEndTime = pendingEffectsRenderEndTime;
const suspendedCommitReason = pendingSuspendedCommitReason;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
recordCommitEndTime();
logCommitPhase(
suspendedCommitReason === null ? completedRenderEndTime : commitStartTime,
commitEndTime,
commitErrors,
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
workInProgressUpdateTask,
);
}
pendingEffectsStatus = PENDING_AFTER_MUTATION_PHASE;
}
39. 刷新生成的工作
备注
recordCommitEndTime()由 ReactProfilerTimer#recordCommitEndTime 实现logStartViewTransitionYieldPhase()由 ReactFiberPerformanceTrack#logStartViewTransitionYieldPhase 实现requestPaint()由 Scheduler#requestPaint 实现includesOnlyViewTransitionEligibleLanes()由 ReactFiberLane#includesOnlyViewTransitionEligibleLanes 实现lanesToEventPriority()由 ReactEventPriorities#lanesToEventPriority 实现onCommitRootDevTools()由 ReactFiberDevToolsHook#onCommitRoot 实现onCommitRootTestSelector()由 ReactTestSelectors#onCommitRoot 实现getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现includesSyncLane()由 ReactFiberLane#includesSyncLane 实现ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现includesSomeLane()由 ReactFiberLane#includesSomeLane 实现markNestedUpdateScheduled()由 ReactProfilerTimer#markNestedUpdateScheduled 实现flushHydrationEvents()由宿主环境提供flushSyncWorkOnAllRoots()由 ReactFiberRootScheduler#flushSyncWorkOnAllRoots 实现markCommitStopped()由 ReactFiberDevToolsHook#markCommitStopped 实现schedulePostPaintCallback()由 ReactPostPaintCallback#schedulePostPaintCallback 实现processTransitionCallbacks()由 ReactFiberTracingMarkerComponent#processTransitionCallbacks 实现
function flushSpawnedWork(): void {
if (
pendingEffectsStatus !== PENDING_SPAWNED_WORK &&
// If a startViewTransition times out, we might flush this earlier than
// after mutation phase. In that case, we just skip the after mutation phase.
//
// 如果 startViewTransition 超时,我们可能会比变更阶段之后更早地刷新。在这种情况下,我们只是
// 跳过变更后的阶段。
pendingEffectsStatus !== PENDING_AFTER_MUTATION_PHASE
) {
return;
}
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// If we didn't skip the after mutation phase, when is means we started an animation.
// 如果我们没有跳过变异后的阶段,这意味着我们开始了一个动画。
const startedAnimation = pendingEffectsStatus === PENDING_SPAWNED_WORK;
if (startedAnimation) {
const startViewTransitionStartTime = commitEndTime;
// Update the new commitEndTime to when we started the animation.
// 将新的 commitEndTime 更新为我们开始动画的时间。
recordCommitEndTime();
logStartViewTransitionYieldPhase(
startViewTransitionStartTime,
commitEndTime,
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
animatingTask,
);
if (pendingDelayedCommitReason !== ABORTED_VIEW_TRANSITION_COMMIT) {
pendingDelayedCommitReason = ANIMATION_STARTED_COMMIT;
}
}
}
pendingEffectsStatus = NO_PENDING_EFFECTS;
// 视图过渡现在已经完全开始。
pendingViewTransition = null; // The view transition has now fully started.
// Tell Scheduler to yield at the end of the frame, so the browser has an
// opportunity to paint.
// 告诉调度器在帧结束时让出,以便浏览器有机会进行绘制。
requestPaint();
const root = pendingEffectsRoot;
const finishedWork = pendingFinishedWork;
const lanes = pendingEffectsLanes;
const recoverableErrors = pendingRecoverableErrors;
const didIncludeRenderPhaseUpdate = pendingDidIncludeRenderPhaseUpdate;
const passiveSubtreeMask =
enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes)
? PassiveTransitionMask
: PassiveMask;
// If this subtree rendered with profiling this commit, we need to visit it to log it.
// 如果这个子树使用剖析渲染了此提交,我们需要访问它以进行记录。
const rootDidHavePassiveEffects =
(enableProfilerTimer &&
enableComponentPerformanceTrack &&
finishedWork.actualDuration !== 0) ||
(finishedWork.subtreeFlags & passiveSubtreeMask) !== NoFlags ||
(finishedWork.flags & passiveSubtreeMask) !== NoFlags;
if (rootDidHavePassiveEffects) {
pendingEffectsStatus = PENDING_PASSIVE_PHASE;
} else {
pendingEffectsStatus = NO_PENDING_EFFECTS;
// 为了垃圾回收目的而清除。
pendingEffectsRoot = null as any; // Clear for GC purposes.
// 为了垃圾回收目的而清除。
pendingFinishedWork = null as any; // Clear for GC purposes.
// There were no passive effects, so we can immediately release the cache
// pool for this render.
// 没有被动效果,所以我们可以立即释放此渲染的缓存池。
releaseRootPooledCache(root, root.pendingLanes);
if (__DEV__) {
nestedPassiveUpdateCount = 0;
rootWithPassiveNestedUpdates = null;
}
}
// Read this again, since an effect might have updated it
// 再次读取,因为某个效果可能已更新它
let remainingLanes = root.pendingLanes;
// Check if there's remaining work on this root
// TODO: This is part of the `componentDidCatch` implementation. Its purpose
// is to detect whether something might have called setState inside
// `componentDidCatch`. The mechanism is known to be flawed because `setState`
// inside `componentDidCatch` is itself flawed — that's why we recommend
// `getDerivedStateFromError` instead. However, it could be improved by
// checking if remainingLanes includes Sync work, instead of whether there's
// any work remaining at all (which would also include stuff like Suspense
// retries or transitions). It's been like this for a while, though, so fixing
// it probably isn't that urgent.
//
// 检查这个 root 上是否还有剩余工作
// TODO: 这是 `componentDidCatch` 实现的一部分。它的目的是检测是否可能有人在 `componentDidCatch`
// 内调用了 setState。这个机制已知有缺陷,因为在 `componentDidCatch` 内使用 `setState` 本身就是
// 有问题的——这也是为什么我们推荐使用 `getDerivedStateFromError`。然而,通过检查 remainingLanes
// 是否包含同步工作,而不是检查是否有任何剩余工作(这还会包括比如 Suspense 重试或过渡等内容),这一点是
// 可以改进的。不过,它已经保持这种状态有一段时间了,所以修复它可能不是特别紧迫。
if (remainingLanes === NoLanes) {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
// 如果没有剩余的工作,我们可以清除已经失败的错误边界集合。
legacyErrorBoundariesThatAlreadyFailed = null;
}
if (__DEV__) {
if (!rootDidHavePassiveEffects) {
commitDoubleInvokeEffectsInDEV(root, false);
}
}
const renderPriority = lanesToEventPriority(lanes);
onCommitRootDevTools(finishedWork.stateNode, renderPriority);
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
root.memoizedUpdaters.clear();
}
}
if (__DEV__) {
onCommitRootTestSelector();
}
if (recoverableErrors !== null) {
const prevTransition = ReactSharedInternals.T;
const previousUpdateLanePriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
ReactSharedInternals.T = null;
try {
// There were errors during this render, but recovered from them without
// needing to surface it to the UI. We log them here.
// 在渲染过程中出现了错误,但已经恢复,无需显示在界面上。我们在这里记录它们。
const onRecoverableError = root.onRecoverableError;
for (let i = 0; i < recoverableErrors.length; i++) {
const recoverableError = recoverableErrors[i];
const errorInfo = makeErrorInfo(recoverableError.stack);
if (__DEV__) {
runWithFiberInDEV(
recoverableError.source,
onRecoverableError,
recoverableError.value,
errorInfo,
);
} else {
onRecoverableError(recoverableError.value, errorInfo);
}
}
} finally {
ReactSharedInternals.T = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority);
}
}
if (enableViewTransition) {
// We should now be after the startViewTransition's .ready call which is late enough
// to start animating any pseudo-elements. We do this before flushing any passive
// effects or spawned sync work since this is still part of the previous commit.
// Even though conceptually it's like its own task between layout effets and passive.
//
// 我们现在应该在 startViewTransition 的 .ready 调用之后,这已经足够晚,可以开始对任何伪元素进行
// 动画处理。在刷新任何被动效果或生成的同步工作之前执行此操作,因为这仍然是前一次提交的一部分。即使从
// 概念上讲,它像是在布局效果和被动效果之间的独立任务。
const pendingEvents = pendingViewTransitionEvents;
let pendingTypes = pendingTransitionTypes;
pendingTransitionTypes = null;
if (pendingEvents !== null) {
pendingViewTransitionEvents = null;
if (pendingTypes === null) {
// Normalize the type. This is lazily created only for events.
// 规范类型。仅为事件懒惰创建。
pendingTypes = [];
}
for (let i = 0; i < pendingEvents.length; i++) {
const viewTransitionEvent = pendingEvents[i];
viewTransitionEvent(pendingTypes);
}
}
}
// If the passive effects are the result of a discrete render, flush them
// synchronously at the end of the current task so that the result is
// immediately observable. Otherwise, we assume that they are not
// order-dependent and do not need to be observed by external systems, so we
// can wait until after paint.
// TODO: We can optimize this by not scheduling the callback earlier. Since we
// currently schedule the callback in multiple places, will wait until those
// are consolidated.
//
// 如果被动效果是由离散渲染导致的,则在当前任务结束时同步刷新它们,以便结果可以立即被观察到。
// 否则,我们假设它们不依赖顺序,也不需要外部系统观察,因此可以等到绘制之后再处理。
// 待办事项:我们可以通过不提前安排回调来优化这一点。由于我们目前在多个地方安排了回调,因此将
// 等到这些地方合并后再处理。
if (
includesSyncLane(pendingEffectsLanes) &&
(disableLegacyMode || root.tag !== LegacyRoot)
) {
flushPendingEffects();
}
// Always call this before exiting `commitRoot`, to ensure that any
// additional work on this root is scheduled.
// 在退出 `commitRoot` 之前总是调用此方法,以确保这个根上的任何额外工作都已安排好。
ensureRootIsScheduled(root);
// Read this again, since a passive effect might have updated it
// 再次阅读此内容,因为被动效果可能已经更新了它
remainingLanes = root.pendingLanes;
// Check if this render scheduled a cascading synchronous update. This is a
// heurstic to detect infinite update loops. We are intentionally excluding
// hydration lanes in this check, because render triggered by selective
// hydration is conceptually not an update.
//
// 检查此渲染是否安排了级联同步更新。这是一种检测无限更新循环的启发式方法。我们有意在此检查中
// 排除了水合通道,因为由选择性水合触发的渲染从概念上并不算更新。
if (
// Check if there was a recursive update spawned by this render, in either
// the render phase or the commit phase. We track these explicitly because
// we can't infer from the remaining lanes alone.
//
// 检查此次渲染是否触发了递归更新,无论是在渲染阶段还是提交阶段。我们会明确跟踪这些,因为
// 仅凭剩余的 lanes 无法推断出来。
(enableInfiniteRenderLoopDetection &&
(didIncludeRenderPhaseUpdate || didIncludeCommitPhaseUpdate)) ||
// Was the finished render the result of an update (not hydration)?
// 完成的渲染是更新的结果吗(而不是重新挂载)?
(includesSomeLane(lanes, UpdateLanes) &&
// Did it schedule a sync update?
// 它安排了同步更新吗?
includesSomeLane(remainingLanes, SyncUpdateLanes))
) {
if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
markNestedUpdateScheduled();
}
// Count the number of times the root synchronously re-renders without
// finishing. If there are too many, it indicates an infinite update loop.
//
// 统计根组件同步重新渲染而未完成的次数。如果次数过多,说明存在无限更新循环。
if (root === rootWithNestedUpdates) {
nestedUpdateCount++;
} else {
nestedUpdateCount = 0;
rootWithNestedUpdates = root;
}
} else {
nestedUpdateCount = 0;
}
if (enableProfilerTimer && enableComponentPerformanceTrack) {
if (!rootDidHavePassiveEffects) {
finalizeRender(lanes, commitEndTime);
}
}
// Eagerly flush any event replaying that we unblocked within this commit.
// This ensures that those are observed before we render any new changes.
//
// 急切地刷新我们在此提交中解锁的任何事件重放。这确保在渲染任何新更改之前,这些事件都能被观察到。
if (supportsHydration) {
flushHydrationEvents();
}
// If layout work was scheduled, flush it now.
// 如果已经安排了布局工作,现在立即刷新它。
flushSyncWorkOnAllRoots();
if (enableSchedulingProfiler) {
markCommitStopped();
}
if (enableTransitionTracing) {
// We process transitions during passive effects. However, passive effects can be
// processed synchronously during the commit phase as well as asynchronously after
// paint. At the end of the commit phase, we schedule a callback that will be called
// after the next paint. If the transitions have already been processed (passive
// effect phase happened synchronously), we will schedule a callback to process
// the transitions. However, if we don't have any pending transition callbacks, this
// means that the transitions have yet to be processed (passive effects processed after paint)
// so we will store the end time of paint so that we can process the transitions
// and then call the callback via the correct end time.
//
// 我们在被动效果期间处理过渡。然而,被动效果可以在提交阶段同步处理,也可以在绘制后异步处理。在提交阶段结束时,我们
// 会安排一个回调,该回调将在下一次绘制后调用。如果过渡已经被处理(被动效果阶段同步发生),我们将安排一个回调来处理
// 过渡。然而,如果我们没有任何待处理的过渡回调,这意味着过渡尚未被处理(被动效果在绘制后处理),所以我们会存储绘制的
// 结束时间,以便我们可以处理过渡,然后通过正确的结束时间调用回调。
const prevRootTransitionCallbacks = root.transitionCallbacks;
if (prevRootTransitionCallbacks !== null) {
schedulePostPaintCallback(endTime => {
const prevPendingTransitionCallbacks =
currentPendingTransitionCallbacks;
if (prevPendingTransitionCallbacks !== null) {
currentPendingTransitionCallbacks = null;
scheduleCallback(IdleSchedulerPriority, () => {
processTransitionCallbacks(
prevPendingTransitionCallbacks,
endTime,
prevRootTransitionCallbacks,
);
});
} else {
currentEndTime = endTime;
}
});
}
}
}
40. 在根节点提交手势
备注
ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现deleteScheduledGesture()由 ReactFiberGestureScheduler#deleteScheduledGesture 实现startAnimating()由 ReactProfilerTimer#startAnimating 实现getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供insertDestinationClones()由 ReactFiberApplyGesture#insertDestinationClones 实现startGestureTransition()由宿主环境提供
function commitGestureOnRoot(
root: FiberRoot,
finishedWork: Fiber,
recoverableErrors: null | Array<CapturedValue<mixed>>,
suspendedState: null | SuspendedState,
// 仅供性能分析用
renderEndTime: number, // Profiling-only
): void {
// We assume that the gesture we just rendered was the first one in the queue.
// 我们假设刚刚渲染的手势是队列中的第一个。
const finishedGesture = root.pendingGestures;
if (finishedGesture === null) {
// We must have already cancelled this gesture before we had a chance to
// render it. Let's schedule work on the next set of lanes.
// 我们一定是在有机会渲染这个手势之前就已经取消了它。我们来安排下一组车道的工作吧。
ensureRootIsScheduled(root);
return;
}
deleteScheduledGesture(root, finishedGesture);
if (enableProfilerTimer && enableComponentPerformanceTrack) {
startAnimating(pendingEffectsLanes);
}
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
try {
insertDestinationClones(root, finishedWork);
} finally {
// Reset the priority to the previous non-sync value.
// 将优先级重置为之前的非同步值。
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
pendingTransitionTypes = finishedGesture.types;
pendingEffectsStatus = PENDING_GESTURE_MUTATION_PHASE;
pendingViewTransition = finishedGesture.running = startGestureTransition(
suspendedState,
root.containerInfo,
finishedGesture.provider,
finishedGesture.rangeStart,
finishedGesture.rangeEnd,
pendingTransitionTypes,
flushGestureMutations,
flushGestureAnimations,
reportViewTransitionError,
enableProfilerTimer
? // This callback fires after "pendingEffects" so we need to snapshot the arguments.
// 这个回调在“pendingEffects”之后触发,所以我们需要快照参数。
finishedViewTransition.bind(null, pendingEffectsLanes)
: (null as any),
);
}
41. 刷新手势变更
备注
getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供applyDepartureTransitions()由 ReactFiberApplyGesture#applyDepartureTransitions 实现
function flushGestureMutations(): void {
if (!enableGestureTransition) {
return;
}
if (pendingEffectsStatus !== PENDING_GESTURE_MUTATION_PHASE) {
return;
}
pendingEffectsStatus = NO_PENDING_EFFECTS;
const root = pendingEffectsRoot;
const finishedWork = pendingFinishedWork;
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
try {
applyDepartureTransitions(root, finishedWork);
} finally {
// Reset the priority to the previous non-sync value.
// 将优先级重置为之前的非同步值。
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
pendingEffectsStatus = PENDING_GESTURE_ANIMATION_PHASE;
}
42. 刷新手势动画
备注
recordCommitEndTime()由 ReactProfilerTimer#recordCommitEndTime 实现logStartViewTransitionYieldPhase()由 ReactFiberPerformanceTrack#logStartViewTransitionYieldPhase 实现getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供startGestureAnimations()由 ReactFiberApplyGesture#startGestureAnimations 实现ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现
function flushGestureAnimations(): void {
if (!enableGestureTransition) {
return;
}
// If we get canceled before we start we might not have applied
// mutations yet. We need to apply them first.
// 如果我们在开始前被取消,可能还没有应用变更。我们需要先应用它们。
flushGestureMutations();
if (pendingEffectsStatus !== PENDING_GESTURE_ANIMATION_PHASE) {
return;
}
const lanes = pendingEffectsLanes;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// Update the new commitEndTime to when we started the animation.
// 将新的 commitEndTime 更新为我们开始动画的时间。
recordCommitEndTime();
logStartViewTransitionYieldPhase(
pendingEffectsRenderEndTime,
commitEndTime,
pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
animatingTask,
);
if (pendingDelayedCommitReason !== ABORTED_VIEW_TRANSITION_COMMIT) {
pendingDelayedCommitReason = ANIMATION_STARTED_COMMIT;
}
}
pendingEffectsStatus = NO_PENDING_EFFECTS;
const root = pendingEffectsRoot;
const finishedWork = pendingFinishedWork;
// 为了垃圾回收目的而清除。
pendingEffectsRoot = null as any; // Clear for GC purposes.
// 为了垃圾回收目的而清除。
pendingFinishedWork = null as any; // Clear for GC purposes.
pendingEffectsLanes = NoLanes;
// 视图过渡现在已经完全开始。
pendingViewTransition = null; // The view transition has now fully started.
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
try {
startGestureAnimations(root, finishedWork);
} finally {
// Reset the priority to the previous non-sync value.
// 将优先级重置为之前的非同步值。
executionContext = prevExecutionContext;
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
}
if (enableProfilerTimer && enableComponentPerformanceTrack) {
finalizeRender(lanes, commitEndTime);
}
// Now that we've rendered this lane. Start working on the next lane.
// 现在我们已经渲染了这条车道。开始处理下一条车道。
ensureRootIsScheduled(root);
}
43. 生成错误信息
function makeErrorInfo(componentStack: ?string) {
const errorInfo = {
componentStack,
};
if (__DEV__) {
Object.defineProperty(errorInfo as any, 'digest', {
get() {
console.error(
'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
' This property is no longer provided as part of errorInfo but can be accessed as a property' +
' of the Error instance itself.',
);
},
});
}
return errorInfo;
}
44. 释放根缓存池
备注
releaseCache()由 ReactFiberCacheComponent#releaseCache 实现
function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) {
const pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes);
if (pooledCacheLanes === NoLanes) {
// None of the remaining work relies on the cache pool. Clear it so
// subsequent requests get a new cache
//
// 剩余的工作都不依赖缓存池。清空它,以便后续请求获取新的缓存
const pooledCache = root.pooledCache;
if (pooledCache != null) {
root.pooledCache = null;
releaseCache(pooledCache);
}
}
}
45. 刷新被动效果
备注
lanesToEventPriority()由 ReactEventPriorities#lanesToEventPriority 实现lowerEventPriority()由 ReactEventPriorities#lowerEventPriority 实现getCurrentUpdatePriority()由宿主环境提供setCurrentUpdatePriority()由宿主环境提供
function flushPassiveEffects(): boolean {
if (pendingEffectsStatus !== PENDING_PASSIVE_PHASE) {
return false;
}
// TODO: Merge flushPassiveEffectsImpl into this function. I believe they were only separate
// in the first place because we used to wrap it with
// `Scheduler.runWithPriority`, which accepts a function. But now we track the
// priority within React itself, so we can mutate the variable directly.
// Cache the root since pendingEffectsRoot is cleared in
// flushPassiveEffectsImpl
//
// 待办事项:将 flushPassiveEffectsImpl 合并到此函数中。我相信它们最初分开只是因为我们曾经用 `Scheduler.runWithPriority` 包裹它,该方法接收一个函数。但现在我们在 React 内部追踪优先级,因此可以直接修改变量。
// 缓存根节点,因为 flushPassiveEffectsImpl 中会清除 pendingEffectsRoot
const root = pendingEffectsRoot;
// Cache and clear the remaining lanes flag; it must be reset since this
// method can be called from various places, not always from commitRoot
// where the remaining lanes are known
//
// 缓存并清除剩余 lanes 标志;必须重置它,因为这个方法可以从不同的地方调用,而不总是从 commitRoot 调用,
// 在那里可以知道剩余的 lanes
const remainingLanes = pendingEffectsRemainingLanes;
pendingEffectsRemainingLanes = NoLanes;
const renderPriority = lanesToEventPriority(pendingEffectsLanes);
const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
const prevTransition = ReactSharedInternals.T;
const previousPriority = getCurrentUpdatePriority();
try {
setCurrentUpdatePriority(priority);
ReactSharedInternals.T = null;
return flushPassiveEffectsImpl();
} finally {
setCurrentUpdatePriority(previousPriority);
ReactSharedInternals.T = prevTransition;
// Once passive effects have run for the tree - giving components a
// chance to retain cache instances they use - release the pooled
// cache at the root (if there is one)
//
// 一旦树的被动效果运行完毕——给组件机会保留它们使用的缓存实例——释放根节点的缓存
// 池(如果存在的话)
releaseRootPooledCache(root, remainingLanes);
}
}
46. 刷新被动效果(实现)
备注
setCurrentTrackFromLanes()由 ReactFiberPerformanceTrack#setCurrentTrackFromLanes 实现resetCommitErrors()由 ReactProfilerTimer#resetCommitErrors 实现now()由 Scheduler#now 实现logAnimatingPhase()由 ReactFiberPerformanceTrack#logAnimatingPhase 实现logPaintYieldPhase()由 ReactFiberPerformanceTrack#logPaintYieldPhase 实现markPassiveEffectsStarted()由 ReactFiberDevToolsHook#markPassiveEffectsStarted 实现commitPassiveUnmountEffects()由 ReactFiberCommitWork#commitPassiveUnmountEffects 实现commitPassiveMountEffects()由 ReactFiberCommitWork#commitPassiveMountEffects 实现markPassiveEffectsStopped()由 ReactFiberDevToolsHook#markPassiveEffectsStopped 实现logPassiveCommitPhase()由 ReactFiberPerformanceTrack#logPassiveCommitPhase 实现flushSyncWorkOnAllRoots()由 ReactFiberRootScheduler#flushSyncWorkOnAllRoots 实现processTransitionCallbacks()由 ReactFiberTracingMarkerComponent#processTransitionCallbacks 实现ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现onPostCommitRootDevTools()由 ReactFiberDevToolsHook#onPostCommitRoot 实现
function flushPassiveEffectsImpl() {
// Cache and clear the transitions flag
// 缓存并清除过渡标志
const transitions = pendingPassiveTransitions;
pendingPassiveTransitions = null;
const root = pendingEffectsRoot;
const lanes = pendingEffectsLanes;
pendingEffectsStatus = NO_PENDING_EFFECTS;
// 为了垃圾回收目的而清除。
pendingEffectsRoot = null as any; // Clear for GC purposes.
// 为了垃圾回收目的而清除。
pendingFinishedWork = null as any; // Clear for GC purposes.
// TODO: This is sometimes out of sync with pendingEffectsRoot.
// Figure out why and fix it. It's not causing any known issues (probably
// because it's only used for profiling), but it's a refactor hazard.
//
// 待办事项:这有时与 pendingEffectsRoot 不同步。找出原因并修复。它目前没有引起已知问题(可能
// 因为它只用于性能分析),但这是一个重构风险。
pendingEffectsLanes = NoLanes;
if (enableYieldingBeforePassive) {
// We've finished our work for this render pass.
// 我们已经完成了本次渲染通道的工作。
root.callbackNode = null;
root.callbackPriority = NoLane;
}
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Cannot flush passive effects while already rendering.');
}
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// We're about to log a lot of profiling for this commit.
// We set this once so we don't have to recompute it for every log.
// 我们即将为此提交记录大量的性能分析。我们只设置一次,这样就不必为每条日志重新计算它。
setCurrentTrackFromLanes(lanes);
}
if (__DEV__) {
isFlushingPassiveEffects = true;
didScheduleUpdateDuringPassiveEffects = false;
}
let passiveEffectStartTime = 0;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
resetCommitErrors();
passiveEffectStartTime = now();
if (pendingDelayedCommitReason === ANIMATION_STARTED_COMMIT) {
// The animation was started, so we've been animating since that happened.
// 动画已经开始,所以自那时起我们一直在进行动画制作。
logAnimatingPhase(commitEndTime, passiveEffectStartTime, animatingTask);
} else {
logPaintYieldPhase(
commitEndTime,
passiveEffectStartTime,
pendingDelayedCommitReason === DELAYED_PASSIVE_COMMIT,
workInProgressUpdateTask,
);
}
}
if (enableSchedulingProfiler) {
markPassiveEffectsStarted(lanes);
}
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(
root,
root.current,
lanes,
transitions,
pendingEffectsRenderEndTime,
);
if (enableSchedulingProfiler) {
markPassiveEffectsStopped();
}
if (__DEV__) {
commitDoubleInvokeEffectsInDEV(root, true);
}
executionContext = prevExecutionContext;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
const passiveEffectsEndTime = now();
logPassiveCommitPhase(
passiveEffectStartTime,
passiveEffectsEndTime,
commitErrors,
workInProgressUpdateTask,
);
finalizeRender(lanes, passiveEffectsEndTime);
}
flushSyncWorkOnAllRoots();
if (enableTransitionTracing) {
const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
const prevRootTransitionCallbacks = root.transitionCallbacks;
const prevEndTime = currentEndTime;
if (
prevPendingTransitionCallbacks !== null &&
prevRootTransitionCallbacks !== null &&
prevEndTime !== null
) {
currentPendingTransitionCallbacks = null;
currentEndTime = null;
scheduleCallback(IdleSchedulerPriority, () => {
processTransitionCallbacks(
prevPendingTransitionCallbacks,
prevEndTime,
prevRootTransitionCallbacks,
);
});
}
}
if (__DEV__) {
// If additional passive effects were scheduled, increment a counter. If this
// exceeds the limit, we'll fire a warning.
//
// 如果计划了额外的被动效果,则增加计数器。如果超过限制,我们将发出警告。
if (didScheduleUpdateDuringPassiveEffects) {
if (root === rootWithPassiveNestedUpdates) {
nestedPassiveUpdateCount++;
} else {
nestedPassiveUpdateCount = 0;
rootWithPassiveNestedUpdates = root;
}
} else {
nestedPassiveUpdateCount = 0;
}
isFlushingPassiveEffects = false;
didScheduleUpdateDuringPassiveEffects = false;
}
if (enableYieldingBeforePassive) {
// Next, we reschedule any remaining work in a new task since it's a new
// sequence of work. We wait until the end to do this in case the passive
// effect schedules higher priority work than we had remaining. That way
// we don't schedule an early callback that gets cancelled anyway.
//
// 接下来,我们将任何剩余的工作重新安排到一个新任务中,因为这是新的一系列工作。我们会等到
// 最后才这样做,以防被动效果安排了比我们剩余工作优先级更高的任务。这样我们就不会提前安排
// 一个最后还是会被取消的回调。
ensureRootIsScheduled(root);
}
// TODO: Move to commitPassiveMountEffects
// 待办:移动到 commitPassiveMountEffects
onPostCommitRootDevTools(root);
if (enableProfilerTimer && enableProfilerCommitHooks) {
const stateNode = root.current.stateNode;
stateNode.effectDuration = 0;
stateNode.passiveEffectDuration = 0;
}
return true;
}
47. 在根上捕获提交阶段错误
备注
createCapturedValueAtFiber()由 ReactCapturedValue#createCapturedValueAtFiber 实现recordEffectError()由 ReactProfilerTimer#recordEffectError 实现createRootErrorUpdate()由 ReactFiberThrow#createRootErrorUpdate 实现enqueueUpdate()由 [ReactFiberClassUpdateQueue#enqueueUpdate 实现ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现
function captureCommitPhaseErrorOnRoot(
rootFiber: Fiber,
sourceFiber: Fiber,
error: mixed,
) {
const errorInfo = createCapturedValueAtFiber(error, sourceFiber);
if (enableProfilerTimer && enableComponentPerformanceTrack) {
recordEffectError(errorInfo);
}
const update = createRootErrorUpdate(
rootFiber.stateNode,
errorInfo,
SyncLane as Lane,
);
const root = enqueueUpdate(rootFiber, update, SyncLane as Lane);
if (root !== null) {
markRootUpdated(root, SyncLane);
ensureRootIsScheduled(root);
}
}
48. 挂起的根节点
备注
startPingTimerByLanes()由 ReactProfilerTimer#startPingTimerByLanes 实现isSubsetOfLanes()由 ReactFiberLane#isSubsetOfLanes 实现includesOnlyRetries()由 ReactFiberLane#includesOnlyRetries 实现mergeLanes()由 ReactFiberLane#mergeLanes 实现ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现
function pingSuspendedRoot(
root: FiberRoot,
wakeable: Wakeable,
pingedLanes: Lanes,
) {
const pingCache = root.pingCache;
if (pingCache !== null) {
// The wakeable resolved, so we no longer need to memoize, because it will
// never be thrown again.
// wakeable 已解决,所以我们不再需要记忆化,因为它不会再被抛出。
pingCache.delete(wakeable);
}
markRootPinged(root, pingedLanes);
if (enableProfilerTimer && enableComponentPerformanceTrack) {
startPingTimerByLanes(pingedLanes);
}
warnIfSuspenseResolutionNotWrappedWithActDEV(root);
if (
workInProgressRoot === root &&
isSubsetOfLanes(workInProgressRootRenderLanes, pingedLanes)
) {
// Received a ping at the same priority level at which we're currently
// rendering. We might want to restart this render. This should mirror
// the logic of whether or not a root suspends once it completes.
// TODO: If we're rendering sync either due to Sync, Batched or expired,
// we should probably never restart.
// TODO: Attach different listeners depending on whether the listener was
// attached during prerendering. Prerender pings should not interrupt
// normal renders.
//
// 在我们当前正在渲染的相同优先级下收到了一个 ping。我们可能需要重新启动此次渲染。这应当
// 与根完成后是否挂起的逻辑相对应。
// TODO:如果我们由于同步、批处理或过期而进行同步渲染,我们可能永远不应该重新启动。
// TODO:根据监听器是在预渲染期间附加的,附加不同的监听器。预渲染的 ping 不应中断正常渲染。
// If we're suspended with delay, or if it's a retry, we'll always suspend
// so we can always restart.
// 如果我们因延迟而被挂起,或者是重试,我们将始终挂起。这样我们就可以随时重新启动。
if (
workInProgressRootExitStatus === RootSuspendedWithDelay ||
(workInProgressRootExitStatus === RootSuspended &&
includesOnlyRetries(workInProgressRootRenderLanes) &&
now() - globalMostRecentFallbackTime < FALLBACK_THROTTLE_MS)
) {
// Force a restart from the root by unwinding the stack. Unless this is
// being called from the render phase, because that would cause a crash.
// 通过展开堆栈强制从根重新启动。除非这是在渲染阶段调用的,因为那样会导致崩溃。
if ((executionContext & RenderContext) === NoContext) {
prepareFreshStack(root, NoLanes);
} else {
// TODO: If this does happen during the render phase, we should throw
// the special internal exception that we use to interrupt the stack for
// selective hydration. That was temporarily reverted but we once we add
// it back we can use it here.
//
// TODO: 如果这在渲染阶段发生,我们应该抛出我们用来中断堆栈进行选择性水化的特殊内部异常。
// 暂时已撤销,但一旦我们恢复它,就可以在这里使用它。
}
} else {
// Even though we can't restart right now, we might get an
// opportunity later. So we mark this render as having a ping.
// 即使我们现在不能重新启动,我们以后可能会有机会。因此,我们把这个渲染标记为有一个 ping。
workInProgressRootPingedLanes = mergeLanes(
workInProgressRootPingedLanes,
pingedLanes,
);
}
// If something pings the work-in-progress render, any work that suspended
// up to this point may now be unblocked; in other words, no
// longer suspended.
//
// 如果某些东西触发了正在进行的渲染,那么到目前为止暂停的任何工作现在都可能被解除阻塞;
// 换句话说,不再暂停。
//
// Unlike the broader check above, we only need do this if the lanes match
// exactly. If the lanes don't exactly match, that implies the promise
// was created by an older render.
//
// 与上面的更广泛检查不同,我们只需要在车道完全匹配时才执行此操作。
// 如果车道不完全匹配,这意味着该 promise 是由较旧的渲染创建的。
if (workInProgressSuspendedRetryLanes === workInProgressRootRenderLanes) {
workInProgressSuspendedRetryLanes = NoLanes;
}
}
ensureRootIsScheduled(root);
}
49. 重试超时边界
备注
enqueueConcurrentRenderForLane()由 ReactFiberConcurrentUpdates#enqueueConcurrentRenderForLane 实现ensureRootIsScheduled()由 ReactFiberRootScheduler#ensureRootIsScheduled 实现
function retryTimedOutBoundary(boundaryFiber: Fiber, retryLane: Lane) {
// The boundary fiber (a Suspense component or SuspenseList component)
// previously was rendered in its fallback state. One of the promises that
// suspended it has resolved, which means at least part of the tree was
// likely unblocked. Try rendering again, at a new lanes.
//
// 边界 fiber(一个 Suspense 组件或 SuspenseList 组件)之前是在其回退状态下渲染的。其中
// 一个导致它挂起的 promise 已经解决,这意味着树的至少一部分可能已解除阻塞。尝试在新的
// lanes 上再次渲染。
if (retryLane === NoLane) {
// TODO: Assign this to `suspenseState.retryLane`? to avoid
// unnecessary entanglement?
// 待办:将其分配给 `suspenseState.retryLane`?以避免不必要的纠缠?
retryLane = requestRetryLane(boundaryFiber);
}
// TODO: Special case idle priority?
// 待办事项:特殊情况空闲优先级?
const root = enqueueConcurrentRenderForLane(boundaryFiber, retryLane);
if (root !== null) {
markRootUpdated(root, retryLane);
ensureRootIsScheduled(root);
}
}
50. 在开发环境中刷新渲染阶段严格模式警告
function flushRenderPhaseStrictModeWarningsInDEV() {
if (__DEV__) {
ReactStrictModeWarnings.flushLegacyContextWarning();
ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
}
}
51. 在开发环境中递归遍历并双重调用副作用
function recursivelyTraverseAndDoubleInvokeEffectsInDEV(
root: FiberRoot,
parentFiber: Fiber,
isInStrictMode: boolean,
) {
if ((parentFiber.subtreeFlags & (PlacementDEV | Visibility)) === NoFlags) {
// Parent's descendants have already had effects double invoked.
// Early exit to avoid unnecessary tree traversal.
//
// 父元素的后代已经被触发了双重效果。提前退出以避免不必要的树遍历。
return;
}
let child = parentFiber.child;
while (child !== null) {
doubleInvokeEffectsInDEVIfNecessary(root, child, isInStrictMode);
child = child.sibling;
}
}
52. 对Fiber的双重调用效果
备注
setIsStrictModeForDevtools()由 ReactFiberDevToolsHook#setIsStrictModeForDevtools 实现disappearLayoutEffects()由 ReactFiberCommitWork#disappearLayoutEffects 实现disconnectPassiveEffect()由 ReactFiberCommitWork#reconnectPassiveEffects 实现reappearLayoutEffects()由 ReactFiberCommitWork#reappearLayoutEffects 实现reconnectPassiveEffects()由 ReactFiberCommitWork#reconnectPassiveEffects 实现
// Unconditionally disconnects and connects passive and layout effects.
// 无条件断开和连接被动效果与布局效果。
function doubleInvokeEffectsOnFiber(root: FiberRoot, fiber: Fiber) {
setIsStrictModeForDevtools(true);
try {
disappearLayoutEffects(fiber);
disconnectPassiveEffect(fiber);
reappearLayoutEffects(root, fiber.alternate, fiber, false);
reconnectPassiveEffects(root, fiber, NoLanes, null, false, 0);
} finally {
setIsStrictModeForDevtools(false);
}
}
53. 在开发环境中如有必要双重调用效果
备注
runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现
function doubleInvokeEffectsInDEVIfNecessary(
root: FiberRoot,
fiber: Fiber,
parentIsInStrictMode: boolean,
) {
const isStrictModeFiber = fiber.type === REACT_STRICT_MODE_TYPE;
const isInStrictMode = parentIsInStrictMode || isStrictModeFiber;
// First case: the fiber **is not** of type OffscreenComponent. No
// special rules apply to double invoking effects.
// 第一种情况:fiber **is not** OffscreenComponent 类型。
// 不会有特殊规则应用于副作用的双重调用。
if (fiber.tag !== OffscreenComponent) {
if (fiber.flags & PlacementDEV) {
if (isInStrictMode) {
runWithFiberInDEV(fiber, doubleInvokeEffectsOnFiber, root, fiber);
}
} else {
recursivelyTraverseAndDoubleInvokeEffectsInDEV(
root,
fiber,
isInStrictMode,
);
}
return;
}
// Second case: the fiber **is** of type OffscreenComponent.
// This branch contains cases specific to Offscreen.
// 第二种情况:fiber **is** OffscreenComponent 类型。
// 这一分支包含 Offscreen 特有的情况。
if (fiber.memoizedState === null) {
// Only consider Offscreen that is visible.
// TODO (Offscreen) Handle manual mode.
// 仅考虑可见的离屏元素。
// 待办(离屏):处理手动模式。
if (isInStrictMode && fiber.flags & Visibility) {
// Double invoke effects on Offscreen's subtree only
// if it is visible and its visibility has changed.
// 仅在 Offscreen 的子树可见且其可见性发生变化时,才双重调用效果。
runWithFiberInDEV(fiber, doubleInvokeEffectsOnFiber, root, fiber);
} else if (fiber.subtreeFlags & PlacementDEV) {
// Something in the subtree could have been suspended.
// We need to continue traversal and find newly inserted fibers.
// 子树中的某些内容可能已被挂起。我们需要继续遍历并找到新插入的 fiber。
runWithFiberInDEV(
fiber,
recursivelyTraverseAndDoubleInvokeEffectsInDEV,
root,
fiber,
isInStrictMode,
);
}
}
}
54. 在开发环境中提交双重调用效果
备注
runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现
function commitDoubleInvokeEffectsInDEV(
root: FiberRoot,
hasPassiveEffects: boolean,
) {
if (__DEV__) {
if (disableLegacyMode || root.tag !== LegacyRoot) {
let doubleInvokeEffects = true;
if (
(disableLegacyMode || root.tag === ConcurrentRoot) &&
!(root.current.mode & (StrictLegacyMode | StrictEffectsMode))
) {
doubleInvokeEffects = false;
}
recursivelyTraverseAndDoubleInvokeEffectsInDEV(
root,
root.current,
doubleInvokeEffects,
);
} else {
// TODO: Is this runWithFiberInDEV needed since the other effect functions do it too?
// TODO: 由于其他 effect 函数也会这样,这个 runWithFiberInDEV 是否还需要?
runWithFiberInDEV(
root.current,
legacyCommitDoubleInvokeEffectsInDEV,
root.current,
hasPassiveEffects,
);
}
}
}
55. 在开发环境中遗留提交双重调用效果
备注
invokeLayoutEffectUnmountInDEV()由 ReactFiberCommitWork#invokeLayoutEffectUnmountInDEV 实现invokePassiveEffectUnmountInDEV()由 ReactFiberCommitWork#invokePassiveEffectUnmountInDEV 实现invokeLayoutEffectMountInDEV()由 ReactFiberCommitWork#invokeLayoutEffectMountInDEV 实现invokePassiveEffectMountInDEV()由 ReactFiberCommitWork#invokePassiveEffectMountInDEV 实现
function legacyCommitDoubleInvokeEffectsInDEV(
fiber: Fiber,
hasPassiveEffects: boolean,
) {
// TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
// so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
// Maybe not a big deal since this is DEV only behavior.
//
// 待办(StrictEffects)如果根节点包含严格副作用,我们是否应该设置一个标记,这样就不必进行不必要的遍历?
// 类似于 subtreeFlags,但只在根节点级别。可能问题不大,因为这只是开发环境下的行为。
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
if (hasPassiveEffects) {
invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectUnmountInDEV);
}
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
if (hasPassiveEffects) {
invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
}
}
56. 在开发中调用效果
function invokeEffectsInDev(
firstChild: Fiber,
fiberFlags: Flags,
invokeEffectFn: (fiber: Fiber) => void,
) {
let current: null | Fiber = firstChild;
let subtreeRoot = null;
while (current != null) {
const primarySubtreeFlag = current.subtreeFlags & fiberFlags;
if (
current !== subtreeRoot &&
current.child != null &&
primarySubtreeFlag !== NoFlags
) {
current = current.child;
} else {
if ((current.flags & fiberFlags) !== NoFlags) {
invokeEffectFn(current);
}
if (current.sibling !== null) {
current = current.sibling;
} else {
current = subtreeRoot = current.return;
}
}
}
}
57. 在开发环境中警告渲染阶段的更新
备注
getComponentNameFromFiber()由宿主环境提供
function warnAboutRenderPhaseUpdatesInDEV(fiber: Fiber) {
if (__DEV__) {
if (ReactCurrentDebugFiberIsRenderingInDEV) {
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
const renderingComponentName =
(workInProgress && getComponentNameFromFiber(workInProgress)) ||
'Unknown';
// Dedupe by the rendering component because it's the one that needs to be fixed.
// 由渲染组件去重复,因为它是需要修复的部分。
const dedupeKey = renderingComponentName;
if (!didWarnAboutUpdateInRenderForAnotherComponent.has(dedupeKey)) {
didWarnAboutUpdateInRenderForAnotherComponent.add(dedupeKey);
const setStateComponentName =
getComponentNameFromFiber(fiber) || 'Unknown';
console.error(
'Cannot update a component (`%s`) while rendering a ' +
'different component (`%s`). To locate the bad setState() call inside `%s`, ' +
'follow the stack trace as described in https://react.dev/link/setstate-in-render',
setStateComponentName,
renderingComponentName,
renderingComponentName,
);
}
break;
}
case ClassComponent: {
if (!didWarnAboutUpdateInRender) {
console.error(
'Cannot update during an existing state transition (such as ' +
'within `render`). Render methods should be a pure ' +
'function of props and state.',
);
didWarnAboutUpdateInRender = true;
}
break;
}
}
}
}
}
58. 安排回调
备注
Scheduler_scheduleCallback()由 Scheduler#scheduleCallback 实现
function scheduleCallback(priorityLevel: any, callback) {
if (__DEV__) {
// If we're currently inside an `act` scope, bypass Scheduler and push to
// the `act` queue instead.
// 如果我们当前在 `act` 范围内,请绕过调度器并改为推入 `act` 队列。
const actQueue = ReactSharedInternals.actQueue;
if (actQueue !== null) {
actQueue.push(callback);
return fakeActCallbackNode;
} else {
return Scheduler_scheduleCallback(priorityLevel, callback);
}
} else {
// In production, always call Scheduler. This function will be stripped out.
// 在生产环境中,始终调用 Scheduler。此函数将在打包时被移除。
return Scheduler_scheduleCallback(priorityLevel, callback);
}
}
59. 是否在开发环境中强制刷新备用方案
function shouldForceFlushFallbacksInDEV() {
// Never force flush in production. This function should get stripped out.
// 切勿在生产环境中强制刷新。此功能应被去除。
return __DEV__ && ReactSharedInternals.actQueue !== null;
}
60. 如果更新未用 act 包裹则发出警告 DEV
备注
isConcurrentActEnvironment()由 ReactFiberAct#isConcurrentActEnvironment 实现isLegacyActEnvironment()由 ReactFiberAct#isLegacyActEnvironment 实现runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现getComponentNameFromFiber()由宿主环境提供
function warnIfUpdatesNotWrappedWithActDEV(fiber: Fiber): void {
if (__DEV__) {
if (disableLegacyMode || fiber.mode & ConcurrentMode) {
if (!isConcurrentActEnvironment()) {
// Not in an act environment. No need to warn.
// 不在 act 环境中。无需警告。
return;
}
} else {
// Legacy mode has additional cases where we suppress a warning.
// 传统模式有一些额外的情况,我们会抑制警告。
if (!isLegacyActEnvironment(fiber)) {
// Not in an act environment. No need to warn.
// 不在 act 环境中。无需警告。
return;
}
if (executionContext !== NoContext) {
// Legacy mode doesn't warn if the update is batched, i.e.
// batchedUpdates or flushSync.
// 旧版模式不会在更新被批处理时发出警告,即 batchedUpdates 或 flushSync。
return;
}
if (
fiber.tag !== FunctionComponent &&
fiber.tag !== ForwardRef &&
fiber.tag !== SimpleMemoComponent
) {
// For backwards compatibility with pre-hooks code, legacy mode only
// warns for updates that originate from a hook.
// 为了与旧版 pre-hooks 代码兼容,遗留模式仅对来自钩子的更新发出警告。
return;
}
}
if (ReactSharedInternals.actQueue === null) {
runWithFiberInDEV(fiber, () => {
console.error(
'An update to %s inside a test was not wrapped in act(...).\n\n' +
'When testing, code that causes React state updates should be ' +
'wrapped into act(...):\n\n' +
'act(() => {\n' +
' /* fire events that update state */\n' +
'});\n' +
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://react.dev/link/wrap-tests-with-act',
getComponentNameFromFiber(fiber),
);
});
}
}
}
61. 警告如果 Suspense 解析未使用 ActDEV 包裹
备注
isConcurrentActEnvironment()由 ReactFiberAct#isConcurrentActEnvironment 实现
function warnIfSuspenseResolutionNotWrappedWithActDEV(root: FiberRoot): void {
if (__DEV__) {
if (
(disableLegacyMode || root.tag !== LegacyRoot) &&
isConcurrentActEnvironment() &&
ReactSharedInternals.actQueue === null
) {
console.error(
'A suspended resource finished loading inside a test, but the event ' +
'was not wrapped in act(...).\n\n' +
'When testing, code that resolves suspended data should be wrapped ' +
'into act(...):\n\n' +
'act(() => {\n' +
' /* finish loading suspended data */\n' +
'});\n' +
'/* assert on the output */\n\n' +
"This ensures that you're testing the behavior the user would see " +
'in the browser.' +
' Learn more at https://react.dev/link/wrap-tests-with-act',
);
}
}
}