跳到主要内容

React Fiber 完成工作

一、作用

二、完成工作

备注
超长,约 1226 行
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
// Note: This intentionally doesn't check if we're hydrating because comparing
// to the current tree provider fiber is just as fast and less error-prone.
// Ideally we would have a special version of the work loop only
// for hydration.
/// 注意:这里故意没有检查我们是否在进行 hydration,因为与当前的 tree provider fiber
// 比较一样快,并且错误更少。 理想情况下,我们会有一个专门用于 hydration 的工作循环版本。
popTreeContext(workInProgress);

switch (workInProgress.tag) {
case IncompleteFunctionComponent: {
if (disableLegacyMode) {
break;
}
// Fallthrough
// 贯穿
}
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
bubbleProperties(workInProgress);
return null;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
bubbleProperties(workInProgress);
return null;
}
case HostRoot: {
const fiberRoot = workInProgress.stateNode as FiberRoot;

if (enableTransitionTracing) {
const transitions = getWorkInProgressTransitions();
// We set the Passive flag here because if there are new transitions,
// we will need to schedule callbacks and process the transitions,
// which we do in the passive phase
//
// 我们在这里设置被动标志,因为如果有新的过渡,我们将需要调度回调并处理过渡,
// 这些操作会在被动阶段进行
if (transitions !== null) {
workInProgress.flags |= Passive;
}
}

let previousCache: Cache | null = null;
if (current !== null) {
previousCache = current.memoizedState.cache;
}
const cache: Cache = workInProgress.memoizedState.cache;
if (cache !== previousCache) {
// Run passive effects to retain/release the cache.
// 运行被动效果以保留/释放缓存。
workInProgress.flags |= Passive;
}
popCacheProvider(workInProgress, cache);

if (enableTransitionTracing) {
popRootMarkerInstance(workInProgress);
}

popRootTransition(workInProgress, fiberRoot, renderLanes);
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
// If we hydrated, pop so that we can delete any remaining children
// that weren't hydrated.
//
// 如果我们进行了 hydrate,则 pop,这样我们可以删除任何未被 hydrate 的剩余子节点。
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
emitPendingHydrationWarnings();
// If we hydrated, then we'll need to schedule an update for
// the commit side-effects on the root.
//
// 如果我们已经进行了hydration,那么我们需要安排一次更新,以处理根节点上的提交副作用。
markUpdate(workInProgress);
} else {
if (current !== null) {
const prevState: RootState = current.memoizedState;
if (
// Check if this is a client root
// 检查这是否是客户端根目录
!prevState.isDehydrated ||
// Check if we reverted to client rendering (e.g. due to an error)
// 检查我们是否回退到客户端渲染(例如,由于错误)
(workInProgress.flags & ForceClientRender) !== NoFlags
) {
// Schedule an effect to clear this container at the start of the
// next commit. This handles the case of React rendering into a
// container with previous children. It's also safe to do for
// updates too, because current.child would only be null if the
// previous render was null (so the container would already
// be empty).
//
// 安排一个 effect,在下一次提交开始时清空这个容器。
// 这处理了 React 渲染到一个已有子节点的容器的情况。
// 对于更新也是安全的,因为 current.child 只有在前一次渲染为 null 时才会为 null(所以容器本身已经是空的)。
workInProgress.flags |= Snapshot;

// If this was a forced client render, there may have been
// recoverable errors during first hydration attempt. If so, add
// them to a queue so we can log them in the commit phase.
//
// 如果这是强制客户端渲染,第一次 hydration 尝试期间可能会有可恢复的错误。
// 如果有,请将它们添加到队列中,以便我们在提交阶段记录它们。
upgradeHydrationErrorsToRecoverable();
}
}
}
}
updateHostContainer(current, workInProgress);
bubbleProperties(workInProgress);
if (enableTransitionTracing) {
if ((workInProgress.subtreeFlags & Visibility) !== NoFlags) {
// If any of our suspense children toggle visibility, this means that
// the pending boundaries array needs to be updated, which we only
// do in the passive phase.
//
// 如果我们有任何 suspense 子组件切换可见性,这意味着待处理的边界数组需要更新,而
// 我们只在被动阶段进行更新。
workInProgress.flags |= Passive;
}
}
return null;
}
case HostHoistable: {
if (supportsResources) {
// The branching here is more complicated than you might expect because
// a HostHoistable sometimes corresponds to a Resource and sometimes
// corresponds to an Instance. It can also switch during an update.
//
// 这里的分支比你想象的要复杂,因为 HostHoistable 有时对应一个资源,有时对应
// 一个实例。它在更新过程中也可能发生切换。

const type = workInProgress.type;
const nextResource: Resource | null = workInProgress.memoizedState;
if (current === null) {
// We are mounting and must Update this Hoistable in this commit
// @TODO refactor this block to create the instance here in complete
// phase if we are not hydrating.
//
// 我们正在挂载,并且必须在此提交中更新此可提升组件
// @TODO 如果我们没有进行水合操作,请重构此块以在完整阶段在此创建实例。
markUpdate(workInProgress);
if (nextResource !== null) {
// This is a Hoistable Resource
// 这是一个可提升的资源

// This must come at the very end of the complete phase.
// 这必须出现在完整阶段的最后。
bubbleProperties(workInProgress);
preloadResourceAndSuspendIfNeeded(
workInProgress,
nextResource,
type,
newProps,
renderLanes,
);
return null;
} else {
// This is a Hoistable Instance
// This must come at the very end of the complete phase.
//
// 这是一个可提升的实例。这必须出现在完整阶段的最末尾。
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(
workInProgress,
type,
null,
newProps,
renderLanes,
);
return null;
}
} else {
// This is an update.
// 这是一次更新。
if (nextResource) {
// This is a Resource
// 这是一个资源
if (nextResource !== current.memoizedState) {
// we have a new Resource. we need to update
// 我们有一个新资源。我们需要更新
markUpdate(workInProgress);
// This must come at the very end of the complete phase.
// 这必须出现在完整阶段的最后。
bubbleProperties(workInProgress);
// This must come at the very end of the complete phase, because it might
// throw to suspend, and if the resource immediately loads, the work loop
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
//
// 这一部分必须放在完整阶段的最后,因为它可能会抛出挂起异常,如果资源立即加载,工作循环
// 将会恢复渲染,好像正在进行的工作已经完成。因此,它必须完全完成。
preloadResourceAndSuspendIfNeeded(
workInProgress,
nextResource,
type,
newProps,
renderLanes,
);
return null;
} else {
// This must come at the very end of the complete phase.
// 这必须出现在完整阶段的最后。
bubbleProperties(workInProgress);
workInProgress.flags &= ~MaySuspendCommit;
return null;
}
} else {
const oldProps = current.memoizedProps;
// This is an Instance
// We may have props to update on the Hoistable instance.
//
// 这是一个实例。我们可能需要更新 Hoistable 实例上的属性。
if (supportsMutation) {
if (oldProps !== newProps) {
markUpdate(workInProgress);
}
} else {
// We use the updateHostComponent path becuase it produces
// the update queue we need for Hoistables.
//
// 我们使用 updateHostComponent 路径,因为它会生成我们在
// Hoistables 中所需的更新队列。
updateHostComponent(
current,
workInProgress,
type,
newProps,
renderLanes,
);
}
// This must come at the very end of the complete phase.
// 这必须出现在完整阶段的最后。
bubbleProperties(workInProgress);
preloadInstanceAndSuspendIfNeeded(
workInProgress,
type,
oldProps,
newProps,
renderLanes,
);
return null;
}
}
}
// Fall through
// 贯穿
}
case HostSingleton: {
if (supportsSingletons) {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
if (supportsMutation) {
const oldProps = current.memoizedProps;
if (oldProps !== newProps) {
markUpdate(workInProgress);
}
} else {
updateHostComponent(
current,
workInProgress,
type,
newProps,
renderLanes,
);
}
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
throw new Error(
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}

// This can happen when we abort work.
// 当我们中止工作时可能会发生这种情况。
bubbleProperties(workInProgress);
if (enableViewTransition) {
// Host Components act as their own View Transitions which doesn't run enter/exit animations.
// We clear any ViewTransitionStatic flag bubbled from inner View Transitions.
//
// 宿主环境组件充当它们自己的视图过渡,不会运行进入/退出动画。
// 我们清除从内部视图过渡冒泡来的任何 ViewTransitionStatic 标志。
workInProgress.subtreeFlags &= ~ViewTransitionStatic;
}
return null;
}

const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
let instance: Instance;
if (wasHydrated) {
// We ignore the boolean indicating there is an updateQueue because
// it is used only to set text children and HostSingletons do not
// use them.
//
// 我们忽略指示是否存在 updateQueue 的布尔值,因为它仅用于设置文本子节点,而
// HostSingleton 并不使用它们。
prepareToHydrateHostInstance(workInProgress, currentHostContext);
instance = workInProgress.stateNode;
} else {
instance = resolveSingletonInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
true,
);
workInProgress.stateNode = instance;
markUpdate(workInProgress);
}
}
bubbleProperties(workInProgress);
if (enableViewTransition) {
// Host Components act as their own View Transitions which doesn't run enter/exit animations.
// We clear any ViewTransitionStatic flag bubbled from inner View Transitions.
//
// 宿主环境组件充当它们自己的视图过渡,不会运行进入/退出动画。
// 我们清除从内部视图过渡冒泡来的任何 ViewTransitionStatic 标志。
workInProgress.subtreeFlags &= ~ViewTransitionStatic;
}
return null;
}
// Fall through
// 贯穿
}
case HostComponent: {
popHostContext(workInProgress);
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
renderLanes,
);
} else {
if (!newProps) {
if (workInProgress.stateNode === null) {
throw new Error(
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}

// This can happen when we abort work.
// 当我们中止工作时可能会发生这种情况。
bubbleProperties(workInProgress);
if (enableViewTransition) {
// Host Components act as their own View Transitions which doesn't run enter/exit animations.
// We clear any ViewTransitionStatic flag bubbled from inner View Transitions.
//
// 宿主环境组件充当它们自己的视图过渡,不会运行进入/退出动画。
// 我们清除从内部视图过渡冒泡来的任何 ViewTransitionStatic 标志。
workInProgress.subtreeFlags &= ~ViewTransitionStatic;
}
return null;
}

const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on whether we want to add them top->down or
// bottom->up. Top->down is faster in IE11.
//
// 待办事项:将 createInstance 移动到 beginWork 并将其保留在上下文的“堆栈”中作为
// 父节点。然后在 beginWork 或 completeWork 中根据我们是想从上到下还是从下到上添
// 加子节点,依次附加子节点。从上到下在 IE11 中更快。
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
//
// 待办:将此步骤和 createInstance 移动到 beginPhase 中,以便整合。
prepareToHydrateHostInstance(workInProgress, currentHostContext);
if (
finalizeHydratedChildren(
workInProgress.stateNode,
type,
newProps,
currentHostContext,
)
) {
workInProgress.flags |= Hydrate;
}
} else {
const rootContainerInstance = getRootHostContainer();
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// TODO: For persistent renderers, we should pass children as part
// of the initial instance creation
//
// 待办事项:对于持久化渲染器,我们应该在初始实例创建时传递子元素
markCloned(workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;

// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
//
// 某些渲染器在初次挂载时需要提交时效应。
// (例如,DOM 渲染器支持某些元素的自动聚焦)。
// 确保这些渲染器安排后续工作。
if (
finalizeInitialChildren(
instance,
type,
newProps,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
}
bubbleProperties(workInProgress);
if (enableViewTransition) {
// Host Components act as their own View Transitions which doesn't run enter/exit animations.
// We clear any ViewTransitionStatic flag bubbled from inner View Transitions.
//
// 宿主环境组件充当它们自己的视图过渡,不会运行进入/退出动画。
// 我们清除从内部视图过渡冒泡来的任何 ViewTransitionStatic 标志。
workInProgress.subtreeFlags &= ~ViewTransitionStatic;
}

// This must come at the very end of the complete phase, because it might
// throw to suspend, and if the resource immediately loads, the work loop
// will resume rendering as if the work-in-progress completed. So it must
// fully complete.
//
// 这一部分必须放在完整阶段的最后,因为它可能会抛出挂起异常,如果资源立即加载,工作循环
// 将会恢复渲染,好像正在进行的工作已经完成。因此,它必须完全完成。
preloadInstanceAndSuspendIfNeeded(
workInProgress,
workInProgress.type,
current === null ? null : current.memoizedProps,
workInProgress.pendingProps,
renderLanes,
);
return null;
}
case HostText: {
const newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
//
// 如果我们有一个替代选项,那意味着这是一次更新,我们需要安排一个副作用来执行更新。
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
if (workInProgress.stateNode === null) {
throw new Error(
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
// This can happen when we abort work.
// 当我们中止工作时可能会发生这种情况。
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
prepareToHydrateHostTextInstance(workInProgress);
} else {
markCloned(workInProgress);
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
bubbleProperties(workInProgress);
return null;
}
case ActivityComponent: {
const nextState: null | ActivityState = workInProgress.memoizedState;

if (current === null || current.memoizedState !== null) {
const fallthroughToNormalOffscreenPath =
completeDehydratedActivityBoundary(
current,
workInProgress,
nextState,
);
if (!fallthroughToNormalOffscreenPath) {
if (workInProgress.flags & ForceClientRender) {
popSuspenseHandler(workInProgress);
// Special case. There were remaining unhydrated nodes. We treat
// this as a mismatch. Revert to client rendering.
//
// 特殊情况。仍有未水合的节点。我们将其视为不匹配。恢复到客户端渲染。
return workInProgress;
} else {
popSuspenseHandler(workInProgress);
// Did not finish hydrating, either because this is the initial
// render or because something suspended.
//
// 未完成水合,要么是因为这是初始渲染,要么是因为某些内容被挂起。
return null;
}
}

if ((workInProgress.flags & DidCapture) !== NoFlags) {
// We called retryActivityComponentWithoutHydrating and tried client rendering
// but now we suspended again. We should never arrive here because we should
// not have pushed a suspense handler during that second pass and it should
// instead have suspended above.
//
// 我们调用了 retryActivityComponentWithoutHydrating 并尝试了客户端渲染
// 但现在我们又挂起了。我们永远不应该到达这里,因为在第二次渲染过程中我们不应
// 推送 suspense 处理程序,而是应该在更上层挂起。
throw new Error(
'Client rendering an Activity suspended it again. This is a bug in React.',
);
}

// Continue with the normal Activity path.
// 继续正常的 Activity 路径。
}

bubbleProperties(workInProgress);
return null;
}
case SuspenseComponent: {
const nextState: null | SuspenseState = workInProgress.memoizedState;

// Special path for dehydrated boundaries. We may eventually move this
// to its own fiber type so that we can add other kinds of hydration
// boundaries that aren't associated with a Suspense tree. In anticipation
// of such a refactor, all the hydration logic is contained in
// this branch.
//
// 针对脱水边界的特殊路径。我们可能最终会将其移到自己独立的 fiber 类型,以便可以添加
// 其他类型的 hydration 边界,而这些边界与 Suspense 树无关。为了预见这种重构,所有
// 的 hydration 逻辑都包含在这个分支中。
if (
current === null ||
(current.memoizedState !== null &&
current.memoizedState.dehydrated !== null)
) {
const fallthroughToNormalSuspensePath =
completeDehydratedSuspenseBoundary(
current,
workInProgress,
nextState,
);
if (!fallthroughToNormalSuspensePath) {
if (workInProgress.flags & ForceClientRender) {
popSuspenseHandler(workInProgress);
// Special case. There were remaining unhydrated nodes. We treat
// this as a mismatch. Revert to client rendering.
//
// 特殊情况。还有未被水合的节点。我们将其视为不匹配。恢复到客户端渲染。
return workInProgress;
} else {
popSuspenseHandler(workInProgress);
// Did not finish hydrating, either because this is the initial
// render or because something suspended.
//
// 未完成水合,要么是因为这是初始渲染,要么是因为某些内容被挂起。
return null;
}
}

// Continue with the normal Suspense path.
// 继续正常的挂起路线。
}

popSuspenseHandler(workInProgress);

if ((workInProgress.flags & DidCapture) !== NoFlags) {
// Something suspended. Re-render with the fallback children.
// 某些内容被挂起。使用备用子元素重新渲染。
workInProgress.lanes = renderLanes;
if (
enableProfilerTimer &&
(workInProgress.mode & ProfileMode) !== NoMode
) {
transferActualDuration(workInProgress);
}
// Don't bubble properties in this case.
// 在这种情况下不要冒泡属性。
return workInProgress;
}

const nextDidTimeout = nextState !== null;
const prevDidTimeout =
current !== null &&
(current.memoizedState as null | SuspenseState) !== null;

if (nextDidTimeout) {
const offscreenFiber: Fiber = workInProgress.child as any;
let previousCache: Cache | null = null;
if (
offscreenFiber.alternate !== null &&
offscreenFiber.alternate.memoizedState !== null &&
offscreenFiber.alternate.memoizedState.cachePool !== null
) {
previousCache = offscreenFiber.alternate.memoizedState.cachePool.pool;
}
let cache: Cache | null = null;
if (
offscreenFiber.memoizedState !== null &&
offscreenFiber.memoizedState.cachePool !== null
) {
cache = offscreenFiber.memoizedState.cachePool.pool;
}
if (cache !== previousCache) {
// Run passive effects to retain/release the cache.
// 运行被动效果以保留/释放缓存。
offscreenFiber.flags |= Passive;
}
}

// If the suspended state of the boundary changes, we need to schedule
// a passive effect, which is when we process the transitions
//
// 如果边界的挂起状态发生变化,我们需要调度一个被动效果,即在处理过渡时执行
if (nextDidTimeout !== prevDidTimeout) {
if (enableTransitionTracing) {
const offscreenFiber: Fiber = workInProgress.child as any;
offscreenFiber.flags |= Passive;
}

// If the suspended state of the boundary changes, we need to schedule
// an effect to toggle the subtree's visibility. When we switch from
// fallback -> primary, the inner Offscreen fiber schedules this effect
// as part of its normal complete phase. But when we switch from
// primary -> fallback, the inner Offscreen fiber does not have a complete
// phase. So we need to schedule its effect here.
//
// 如果边界的挂起状态发生变化,我们需要安排一个 effect 来切换子树的可见性。当我们从
// fallback -> primary 切换时,内部的 Offscreen fiber 会在其正常完成阶段调度这个 effect。
// 但是当我们从 primary -> fallback 切换时,内部的 Offscreen fiber 没有完成阶段。
// 因此我们需要在这里调度它的 effect。
//
// We also use this flag to connect/disconnect the effects, but the same
// logic applies: when re-connecting, the Offscreen fiber's complete
// phase will handle scheduling the effect. It's only when the fallback
// is active that we have to do anything special.
//
// 我们也使用这个标志来连接/断开效果,但逻辑是相同的:当重新连接时,离屏 fiber 的完成
// 阶段会处理调度效果。只有在回退(fallback)激活时,我们才需要做任何特殊处理。
if (nextDidTimeout) {
const offscreenFiber: Fiber = workInProgress.child as any;
offscreenFiber.flags |= Visibility;
}
}

const retryQueue: RetryQueue | null = workInProgress.updateQueue as any;
scheduleRetryEffect(workInProgress, retryQueue);

if (
enableSuspenseCallback &&
workInProgress.updateQueue !== null &&
workInProgress.memoizedProps.suspenseCallback != null
) {
// Always notify the callback
// TODO: Move to passive phase
//
// 始终通知回调
// 待办事项:移至被动阶段
workInProgress.flags |= Update;
}
bubbleProperties(workInProgress);
if (enableProfilerTimer) {
if ((workInProgress.mode & ProfileMode) !== NoMode) {
if (nextDidTimeout) {
// Don't count time spent in a timed out Suspense subtree as part of the base duration.
// 不要将超时的 Suspense 子树中花费的时间计入基本持续时间。
const primaryChildFragment = workInProgress.child;
if (primaryChildFragment !== null) {
workInProgress.treeBaseDuration -=
primaryChildFragment.treeBaseDuration as any as number;
}
}
}
}
return null;
}
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(current, workInProgress);
if (current === null) {
preparePortalMount(workInProgress.stateNode.containerInfo);
}
workInProgress.flags |= PortalStatic;
bubbleProperties(workInProgress);
return null;
case ContextProvider:
// Pop provider fiber
// 弹出提供者 fiber
const context: ReactContext<any> = workInProgress.type;
popProvider(context, workInProgress);
bubbleProperties(workInProgress);
return null;
case IncompleteClassComponent: {
if (disableLegacyMode) {
break;
}
// Same as class component case. I put it down here so that the tags are
// sequential to ensure this switch is compiled to a jump table.
//
// 与类组件的情况相同。我把它放在这里,以确保标签是顺序的,从而确保此 switch 被编译为跳转表。
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
bubbleProperties(workInProgress);
return null;
}
case SuspenseListComponent: {
popSuspenseListContext(workInProgress);

const renderState: null | SuspenseListRenderState =
workInProgress.memoizedState;

if (renderState === null) {
// We're running in the default, "independent" mode.
// We don't do anything in this mode.
//
// 我们正在以默认的“独立”模式运行。在此模式下,我们不会执行任何操作。
bubbleProperties(workInProgress);
return null;
}

let didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;

const renderedTail = renderState.rendering;
if (renderedTail === null) {
// We just rendered the head.
// 我们刚刚渲染了头部。
if (!didSuspendAlready) {
// This is the first pass. We need to figure out if anything is still
// suspended in the rendered set.
//
// 这是第一次处理。我们需要弄清楚渲染集合中是否还有任何内容处于挂起状态。

// If new content unsuspended, but there's still some content that
// didn't. Then we need to do a second pass that forces everything
// to keep showing their fallbacks.
//
// 如果新的内容已被解除暂停,但仍有一些内容没有解除暂停,那么我们需要进行第二轮
// 处理,强制所有内容继续显示其备用内容。

// We might be suspended if something in this render pass suspended, or
// something in the previous committed pass suspended. Otherwise,
// there's no chance so we can skip the expensive call to
// findFirstSuspended.
//
// 如果这次渲染过程中有内容挂起,或者之前已提交的渲染过程有内容挂起,我们可能会被挂起。
// 否则,没有这种可能,所以我们可以跳过昂贵的 findFirstSuspended 调用。
const cannotBeSuspended =
renderHasNotSuspendedYet() &&
(current === null || (current.flags & DidCapture) === NoFlags);
if (!cannotBeSuspended) {
let row = workInProgress.child;
while (row !== null) {
const suspended = findFirstSuspended(row);
if (suspended !== null) {
didSuspendAlready = true;
workInProgress.flags |= DidCapture;
cutOffTailIfNeeded(renderState, false);

// If this is a newly suspended tree, it might not get committed as
// part of the second pass. In that case nothing will subscribe to
// its thenables. Instead, we'll transfer its thenables to the
// SuspenseList so that it can retry if they resolve.
// There might be multiple of these in the list but since we're
// going to wait for all of them anyway, it doesn't really matter
// which ones gets to ping. In theory we could get clever and keep
// track of how many dependencies remain but it gets tricky because
// in the meantime, we can add/remove/change items and dependencies.
// We might bail out of the loop before finding any but that
// doesn't matter since that means that the other boundaries that
// we did find already has their listeners attached.
//
// 如果这是一个新挂起的树,它可能不会在第二次遍历时被提交。在那种情况下,没有东西会订阅它
// 的 thenable。相反,我们会将它的 thenable 转移到 SuspenseList,以便它在 thenable
// 解决时可以重试。
// 列表中可能会有多个这样的对象,但由于我们无论如何都会等待它们全部完成,所以哪个对象先触发其实无关
// 紧要。理论上,我们可以更聪明地跟踪剩余的依赖数量,但这很复杂,因为在这期间,我们可能会添加、删除
// 或更改项目和依赖项。 我们可能在找到任何依赖项之前就退出循环,但这无关紧要,因为这意味着我们已经
// 找到的其他边界已经附加了它们的监听器。
const retryQueue: RetryQueue | null =
suspended.updateQueue as any;
workInProgress.updateQueue = retryQueue;
scheduleRetryEffect(workInProgress, retryQueue);

// Rerender the whole list, but this time, we'll force fallbacks
// to stay in place.
// Reset the effect flags before doing the second pass since that's now invalid.
// Reset the child fibers to their original state.
//
// 重新渲染整个列表,但这次我们将强制保留回退。
// 在执行第二次遍历之前重置 effect 标记,因为它们现在无效。
// 将子 fiber 重置回它们的原始状态。
workInProgress.subtreeFlags = NoFlags;
resetChildFibers(workInProgress, renderLanes);

// Set up the Suspense List Context to force suspense and
// immediately rerender the children.
// 设置 Suspense 列表上下文以强制 suspense 并
// 立即重新渲染子组件。
pushSuspenseListContext(
workInProgress,
setShallowSuspenseListContext(
suspenseStackCursor.current,
ForceSuspenseFallback,
),
);
if (getIsHydrating()) {
// Re-apply tree fork since we popped the tree fork context in the beginning of this function.
// 重新应用树分叉,因为我们在函数开始时弹出了树分叉上下文。
pushTreeFork(workInProgress, renderState.treeForkCount);
}
// Don't bubble properties in this case.
// 在这种情况下不要冒泡属性。
return workInProgress.child;
}
row = row.sibling;
}
}

if (renderState.tail !== null && now() > getRenderTargetTime()) {
// We have already passed our CPU deadline but we still have rows
// left in the tail. We'll just give up further attempts to render
// the main content and only render fallbacks.
//
// 我们已经超过了 CPU 截止时间,但尾部仍有行存在。
// 我们将放弃进一步尝试渲染主要内容,只渲染备用内容。
workInProgress.flags |= DidCapture;
didSuspendAlready = true;

cutOffTailIfNeeded(renderState, false);

// Since nothing actually suspended, there will nothing to ping this
// to get it started back up to attempt the next item. While in terms
// of priority this work has the same priority as this current render,
// it's not part of the same transition once the transition has
// committed. If it's sync, we still want to yield so that it can be
// painted. Conceptually, this is really the same as pinging.
// We can use any RetryLane even if it's the one currently rendering
// since we're leaving it behind on this node.
//
// 因为实际上没有任何内容被挂起,所以不会有任何东西去 ping 它来重新启动以尝试下一个任务。
// 就优先级而言,这项工作与当前渲染的优先级相同,但一旦过渡已提交,它不属于同一过渡。如果
// 是同步的,我们仍然希望让出以便它可以被绘制。从概念上讲,这实际上与 ping 是一样的。我们
// 可以使用任何重试通道,即使它是当前正在渲染的通道,因为我们会在这个节点上将它留下。
workInProgress.lanes = SomeRetryLane;
}
} else {
cutOffTailIfNeeded(renderState, false);
}
// Next we're going to render the tail.
// 接下来我们将渲染尾巴
} else {
// Append the rendered row to the child list.
// 将渲染的行追加到子列表中。
if (!didSuspendAlready) {
const suspended = findFirstSuspended(renderedTail);
if (suspended !== null) {
workInProgress.flags |= DidCapture;
didSuspendAlready = true;

// Ensure we transfer the update queue to the parent so that it doesn't
// get lost if this row ends up dropped during a second pass.
//
// 确保我们将更新队列转移到父元素,这样如果这一行在第二次遍历时被删除,它就不会丢失。
const retryQueue: RetryQueue | null = suspended.updateQueue as any;
workInProgress.updateQueue = retryQueue;
scheduleRetryEffect(workInProgress, retryQueue);

cutOffTailIfNeeded(renderState, true);
// This might have been modified.
// 这可能已经被修改。
if (
renderState.tail === null &&
renderState.tailMode !== 'collapsed' &&
renderState.tailMode !== 'visible' &&
!renderedTail.alternate &&
// 如果我们正在补水,就不删减它。
!getIsHydrating() // We don't cut it if we're hydrating.
) {
// We're done.
// 我们完成了。
bubbleProperties(workInProgress);
return null;
}
} else if (
// The time it took to render last row is greater than the remaining
// time we have to render. So rendering one more row would likely
// exceed it.
//
// 我们现在已经超过了 CPU 截止时间,我们将放弃进一步尝试渲染主要内容,只渲染备用内容。
// 假设这样通常更快。
now() * 2 - renderState.renderingStartTime >
getRenderTargetTime() &&
renderLanes !== OffscreenLane
) {
// We have now passed our CPU deadline and we'll just give up further
// attempts to render the main content and only render fallbacks.
// The assumption is that this is usually faster.
workInProgress.flags |= DidCapture;
didSuspendAlready = true;

cutOffTailIfNeeded(renderState, false);

// Since nothing actually suspended, there will nothing to ping this
// to get it started back up to attempt the next item. While in terms
// of priority this work has the same priority as this current render,
// it's not part of the same transition once the transition has
// committed. If it's sync, we still want to yield so that it can be
// painted. Conceptually, this is really the same as pinging.
// We can use any RetryLane even if it's the one currently rendering
// since we're leaving it behind on this node.
//
// 因为实际上没有任何内容被挂起,所以不会有任何东西去 ping 它来重新启动以尝试下一个任务。
// 就优先级而言,这项工作与当前渲染的优先级相同,但一旦过渡已提交,它不属于同一过渡。如果
// 是同步的,我们仍然希望让出以便它可以被绘制。从概念上讲,这实际上与 ping 是一样的。我们
// 可以使用任何重试通道,即使它是当前正在渲染的通道,因为我们会在这个节点上将它留下。
workInProgress.lanes = SomeRetryLane;
}
}
if (renderState.isBackwards) {
// Append to the beginning of the list.
// 添加到列表的开头。
renderedTail.sibling = workInProgress.child;
workInProgress.child = renderedTail;
} else {
const previousSibling = renderState.last;
if (previousSibling !== null) {
previousSibling.sibling = renderedTail;
} else {
workInProgress.child = renderedTail;
}
renderState.last = renderedTail;
}
}

if (renderState.tail !== null) {
// We still have tail rows to render.
// Pop a row.
// TODO: Consider storing the first of the new mount tail in the state so
// that we don't have to recompute this for every row in the list.
//
// 我们仍然有尾部行要渲染。
// 弹出一行。
// TODO:考虑将新挂载尾部的第一行存储在状态中,这样我们就不必为列表中的每一行重复计算这个值。
const next = renderState.tail;
const onlyNewMounts = isOnlyNewMounts(next);
renderState.rendering = next;
renderState.tail = next.sibling;
renderState.renderingStartTime = now();
next.sibling = null;

// Restore the context.
// TODO: We can probably just avoid popping it instead and only
// setting it the first time we go from not suspended to suspended.
//
// 恢复上下文。
// 待办事项:我们可能可以直接避免弹出它,而只在第一次从非挂起转为挂起时设置它。
let suspenseContext = suspenseStackCursor.current;
if (didSuspendAlready) {
suspenseContext = setShallowSuspenseListContext(
suspenseContext,
ForceSuspenseFallback,
);
} else {
suspenseContext =
setDefaultShallowSuspenseListContext(suspenseContext);
}
if (
renderState.tailMode === 'visible' ||
renderState.tailMode === 'collapsed' ||
!onlyNewMounts ||
// TODO: While hydrating, we still let it suspend the parent. Tail mode hidden has broken
// hydration anyway right now but this preserves the previous semantics out of caution.
// Once proper hydration is implemented, this special case should be removed as it should
// never be needed.
//
// 待办事项:在进行 hydration 时,我们仍然让它挂起父组件。尾部模式隐藏目前无论如何都会破坏 hydration,但
// 出于谨慎,这保留了之前的语义。一旦实现了正确的 hydration,这个特殊情况应该被移除,因为它永远不应该需要。
getIsHydrating()
) {
pushSuspenseListContext(workInProgress, suspenseContext);
} else {
// If we are rendering in 'hidden' (default) tail mode, then we if we suspend in the
// tail itself, we can delete it rather than suspend the parent. So we act as a catch in that
// case. For 'collapsed' we need to render at least one in suspended state, after which we'll
// have cut off the rest to never attempt it so it never hits this case.
// If this is an updated node, we cannot delete it from the tail so it's effectively visible.
// As a consequence, if it resuspends it actually suspends the parent by taking the other path.
//
// 如果我们以“隐藏”(默认)尾部模式渲染,那么如果我们在尾部本身,我们可以删除它,而不是暂停父节点。所以我们作为
// 一个陷阱箱。对于“折叠”,我们需要至少渲染一个处于悬浮状态,之后我们将我已经切断了剩下的,永远不尝试,这样就不
// 会碰到这个案子。如果这是一个更新的节点,我们无法从尾部删除它,所以它实际上是可见的。
// 因此,如果它重新挂起,实际上是通过走另一条路径来暂停父节点。

pushSuspenseListCatch(workInProgress, suspenseContext);
}
// Do a pass over the next row.
if (getIsHydrating()) {
// Re-apply tree fork since we popped the tree fork context in the beginning
// of this function.
//
// 重新应用树分叉,因为我们在函数开始时弹出了树分叉上下文。
pushTreeFork(workInProgress, renderState.treeForkCount);
}
// Don't bubble properties in this case.
// 在这种情况下不要冒泡属性。
return next;
}
bubbleProperties(workInProgress);
return null;
}
case ScopeComponent: {
if (enableScopeAPI) {
if (current === null) {
const scopeInstance: ReactScopeInstance = createScopeInstance();
workInProgress.stateNode = scopeInstance;
prepareScopeUpdate(scopeInstance, workInProgress);
if (workInProgress.ref !== null) {
// Scope components always do work in the commit phase if there's a
// ref attached.
//
// 如果有附加的 ref,范围组件始终会在提交阶段工作。
markUpdate(workInProgress);
}
} else {
if (workInProgress.ref !== null) {
// Scope components always do work in the commit phase if there's a
// ref attached.
//
// 如果有附加的 ref,范围组件始终会在提交阶段工作。
markUpdate(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
break;
}
case OffscreenComponent:
case LegacyHiddenComponent: {
popSuspenseHandler(workInProgress);
popHiddenContext(workInProgress);
const nextState: OffscreenState | null = workInProgress.memoizedState;
const nextIsHidden = nextState !== null;

// Schedule a Visibility effect if the visibility has changed
// 如果可见性发生变化,则安排可见性效果
if (enableLegacyHidden && workInProgress.tag === LegacyHiddenComponent) {
// LegacyHidden doesn't do any hiding — it only pre-renders.
// LegacyHidden 并不会进行任何隐藏 — 它只会进行预渲染。
} else {
if (current !== null) {
const prevState: OffscreenState | null = current.memoizedState;
const prevIsHidden = prevState !== null;
if (prevIsHidden !== nextIsHidden) {
workInProgress.flags |= Visibility;
}
} else {
// On initial mount, we only need a Visibility effect if the tree
// is hidden.
//
// 在初次挂载时,只有当树隐藏时,我们才需要可见性效果。
if (nextIsHidden) {
workInProgress.flags |= Visibility;
}
}
}

if (
!nextIsHidden ||
(!disableLegacyMode &&
(workInProgress.mode & ConcurrentMode) === NoMode)
) {
bubbleProperties(workInProgress);
} else {
// Don't bubble properties for hidden children unless we're rendering
// at offscreen priority.
//
// 除非我们以离屏优先级进行渲染,否则不要为隐藏的子元素冒泡属性。
if (
includesSomeLane(renderLanes, OffscreenLane as Lane) &&
// Also don't bubble if the tree suspended
// 如果树被挂起,也不要冒泡
(workInProgress.flags & DidCapture) === NoLanes
) {
bubbleProperties(workInProgress);
// Check if there was an insertion or update in the hidden subtree.
// If so, we need to hide those nodes in the commit phase, so
// schedule a visibility effect.
//
// 检查隐藏子树中是否有插入或更新。如果有,我们需要在提交阶段隐藏这些节点,因此
// 安排一个可见性效果。
if (
(!enableLegacyHidden ||
workInProgress.tag !== LegacyHiddenComponent) &&
workInProgress.subtreeFlags & (Placement | Update)
) {
workInProgress.flags |= Visibility;
}
}
}

const offscreenQueue: OffscreenQueue | null =
workInProgress.updateQueue as any;
if (offscreenQueue !== null) {
const retryQueue = offscreenQueue.retryQueue;
scheduleRetryEffect(workInProgress, retryQueue);
}

let previousCache: Cache | null = null;
if (
current !== null &&
current.memoizedState !== null &&
current.memoizedState.cachePool !== null
) {
previousCache = current.memoizedState.cachePool.pool;
}
let cache: Cache | null = null;
if (
workInProgress.memoizedState !== null &&
workInProgress.memoizedState.cachePool !== null
) {
cache = workInProgress.memoizedState.cachePool.pool;
}
if (cache !== previousCache) {
// Run passive effects to retain/release the cache.
// 运行被动效果以保留/释放缓存。
workInProgress.flags |= Passive;
}

popTransition(workInProgress, current);

return null;
}
case CacheComponent: {
let previousCache: Cache | null = null;
if (current !== null) {
previousCache = current.memoizedState.cache;
}
const cache: Cache = workInProgress.memoizedState.cache;
if (cache !== previousCache) {
// Run passive effects to retain/release the cache.
// 运行被动效果以保留/释放缓存。
workInProgress.flags |= Passive;
}
popCacheProvider(workInProgress, cache);
bubbleProperties(workInProgress);
return null;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
const instance: TracingMarkerInstance | null = workInProgress.stateNode;
if (instance !== null) {
popMarkerInstance(workInProgress);
}
bubbleProperties(workInProgress);
}
return null;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
// We're a component that might need an exit transition. This flag will
// bubble up to the parent tree to indicate that there's a child that
// might need an exit View Transition upon unmount.
//
// 我们是一个可能需要退出过渡的组件。这个标志会向父级树冒泡,以指示有一个子组件在卸载时
// 可能需要一个退出视图过渡。
workInProgress.flags |= ViewTransitionStatic;
bubbleProperties(workInProgress);
}
return null;
}
case Throw: {
if (!disableLegacyMode) {
// Only Legacy Mode completes an errored node.
// 只有传统模式会完成出错的节点。
return null;
}
}
}

throw new Error(
`Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
'React. Please file an issue.',
);
}

三、工具

1. 标记更新

/**
* Tag the fiber with an update effect. This turns a Placement into
* a PlacementAndUpdate.
*
* 给 fiber 添加一个更新效果。这会将 Placement 转变为 PlacementAndUpdate。
*/
function markUpdate(workInProgress: Fiber) {
workInProgress.flags |= Update;
}

2. 标记克隆

/**
* Tag the fiber with Cloned in persistent mode to signal that
* it received an update that requires a clone of the tree above.
*
* 在持久模式下将 fiber 标记为 Cloned,以表示它接收到需要克隆上层树的更新。
*/
function markCloned(workInProgress: Fiber) {
if (supportsPersistence) {
workInProgress.flags |= Cloned;
}
}

3. 标记已克隆

/**
* Tag the fiber with Cloned in persistent mode to signal that
* it received an update that requires a clone of the tree above.
*
* 在持久模式下将 fiber 标记为 Cloned,以表示它接收到需要克隆上方树的更新。
*/
function markCloned(workInProgress: Fiber) {
if (supportsPersistence) {
workInProgress.flags |= Cloned;
}
}

4. 是否需要克隆

/**
* In persistent mode, return whether this update needs to clone the subtree.
* 在持久模式下,返回此更新是否需要克隆子树。
*/
function doesRequireClone(current: null | Fiber, completedWork: Fiber) {
const didBailout = current !== null && current.child === completedWork.child;
if (didBailout) {
return false;
}

if ((completedWork.flags & ChildDeletion) !== NoFlags) {
return true;
}

// TODO: If we move the `doesRequireClone` call after `bubbleProperties`
// then we only have to check the `completedWork.subtreeFlags`.
//
// 待办:如果我们把 `doesRequireClone` 调用移到 `bubbleProperties` 之后
// 那么我们只需要检查 `completedWork.subtreeFlags`。
let child = completedWork.child;
while (child !== null) {
const checkedFlags = Cloned | Visibility | Placement | ChildDeletion;
if (
(child.flags & checkedFlags) !== NoFlags ||
(child.subtreeFlags & checkedFlags) !== NoFlags
) {
return true;
}
child = child.sibling;
}
return false;
}

5. 追加所有子节点

备注
  • appendInitialChild() 由 宿主环境提供
  • cloneHiddenInstance() 由 宿主环境提供
  • cloneHiddenTextInstance() 由 宿主环境提供
function appendAllChildren(
parent: Instance,
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
) {
if (supportsMutation) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
//
// 我们只有创建好的顶层 Fiber,但我们需要递归其子节点以找到所有终端节点。
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (
node.tag === HostPortal ||
(supportsSingletons ? node.tag === HostSingleton : false)
) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
// If we have a HostSingleton it will be placed independently
//
// 如果我们有一个 portal 子元素,那么我们不想遍历它的子元素。
// 相反,我们将直接从 portal 中的每个子元素获取插入。
// 如果我们有一个 HostSingleton,它将独立放置
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
} else if (supportsPersistence) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
//
// 我们只有创建好的顶层 Fiber,但我们需要递归其子节点以找到所有终端节点。
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent) {
let instance = node.stateNode;
if (needsVisibilityToggle && isHidden) {
// This child is inside a timed out tree. Hide it.
// 这个孩子在一个超时的树里面。隐藏它。
const props = node.memoizedProps;
const type = node.type;
instance = cloneHiddenInstance(instance, type, props);
}
appendInitialChild(parent, instance);
} else if (node.tag === HostText) {
let instance = node.stateNode;
if (needsVisibilityToggle && isHidden) {
// This child is inside a timed out tree. Hide it.
// 这个孩子在一个超时的树里面。隐藏它。
const text = node.memoizedProps;
instance = cloneHiddenTextInstance(instance, text);
}
appendInitialChild(parent, instance);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
//
// 如果我们有一个 portal 子元素,那么我们不想遍历它的子元素。
// 相反,我们将直接从 portal 中的每个子元素获取插入内容。
} else if (
node.tag === OffscreenComponent &&
node.memoizedState !== null
) {
// The children in this boundary are hidden. Toggle their visibility
// before appending.
//
// 此边界内的子元素是隐藏的。在添加它们之前切换它们的可见性。
const child = node.child;
if (child !== null) {
child.return = node;
}
// + function appendAllChildren(parent: Instance, workInProgress: Fiber,
// + needsVisibilityToggle: boolean, isHidden: boolean): void
appendAllChildren(parent, node, true, true);
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
}

6. 将所有子元素添加到容器

备注
  • cloneHiddenInstance() 由 宿主环境提供
  • appendChildToContainerChildSet() 由 宿主环境提供
  • cloneHiddenTextInstance() 由 宿主环境提供
  • appendChildToContainerChildSet() 由 宿主环境提供
// An unfortunate fork of appendAllChildren because we have two different parent types.
// appendAllChildren 的一个不幸的分叉,因为我们有两种不同的父类型。
function appendAllChildrenToContainer(
containerChildSet: ChildSet,
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
): boolean {
// Host components that have their visibility toggled by an OffscreenComponent
// do not support passChildrenWhenCloningPersistedNodes. To inform the callee
// about their presence, we track and return if they were added to the
// child set.
//
// 由 OffscreenComponent 切换可见性的宿主组件不支持 passChildrenWhenCloningPersistedNodes。
// 为了通知被调用方它们的存在,我们会跟踪并返回它们是否已被添加到子组件集合中。
let hasOffscreenComponentChild = false;
if (supportsPersistence) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
//
// 我们只有创建好的顶层 Fiber,但我们需要递归其子节点以找到所有终端节点。
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent) {
let instance = node.stateNode;
if (needsVisibilityToggle && isHidden) {
// This child is inside a timed out tree. Hide it.
// 这个孩子在一个超时的树里面。隐藏它。
const props = node.memoizedProps;
const type = node.type;
instance = cloneHiddenInstance(instance, type, props);
}
appendChildToContainerChildSet(containerChildSet, instance);
} else if (node.tag === HostText) {
let instance = node.stateNode;
if (needsVisibilityToggle && isHidden) {
// This child is inside a timed out tree. Hide it.
// 这个孩子在一个超时的树里面。隐藏它。
const text = node.memoizedProps;
instance = cloneHiddenTextInstance(instance, text);
}
appendChildToContainerChildSet(containerChildSet, instance);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
//
// 如果我们有一个 portal 子元素,那么我们不想遍历它的子元素。
// 相反,我们将直接从 portal 中的每个子元素获取插入内容。
} else if (
node.tag === OffscreenComponent &&
node.memoizedState !== null
) {
// The children in this boundary are hidden. Toggle their visibility
// before appending.
// 此边界内的子元素是隐藏的。在添加它们之前切换它们的可见性。
const child = node.child;
if (child !== null) {
child.return = node;
}
// function appendAllChildrenToContainer(containerChildSet: ChildSet,
// workInProgress: Fiber, needsVisibilityToggle: boolean,
// isHidden: boolean): boolean
appendAllChildrenToContainer(containerChildSet, node, true, true);

hasOffscreenComponentChild = true;
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
node = node as Fiber;
if (node === workInProgress) {
return hasOffscreenComponentChild;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return hasOffscreenComponentChild;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}

return hasOffscreenComponentChild;
}

7. 更新宿主环境容器

备注
  • createContainerChildSet() 由 宿主环境提供
  • finalizeContainerChildren() 由 宿主环境提供
function updateHostContainer(current: null | Fiber, workInProgress: Fiber) {
if (supportsPersistence) {
if (doesRequireClone(current, workInProgress)) {
const portalOrRoot: {
containerInfo: Container;
pendingChildren: ChildSet;
// - ...
} = workInProgress.stateNode;
const container = portalOrRoot.containerInfo;
const newChildSet = createContainerChildSet();
// If children might have changed, we have to add them all to the set.
// 如果子节点可能已更改,我们必须将它们全部添加到集合中。
// function appendAllChildrenToContainer(containerChildSet: ChildSet,
// workInProgress: Fiber, needsVisibilityToggle: boolean,
// isHidden: boolean): boolean
appendAllChildrenToContainer(newChildSet, workInProgress, false, false);
portalOrRoot.pendingChildren = newChildSet;
// Schedule an update on the container to swap out the container.
// 安排对容器的更新以更换容器。
markUpdate(workInProgress);
finalizeContainerChildren(container, newChildSet);
}
}
}

8. 更新宿主环境组件

备注
  • getHostContext()ReactFiberHostContext#getHostContext 实现
  • createContainerChildSet() 由 宿主环境提供
  • cloneInstance() 由 宿主环境提供
  • finalizeInitialChildren() 由 宿主环境提供
function updateHostComponent(
current: Fiber,
workInProgress: Fiber,
type: Type,
newProps: Props,
renderLanes: Lanes,
) {
if (supportsMutation) {
// If we have an alternate, that means this is an update and we need to
// schedule a side-effect to do the updates.
//
// 如果我们有一个替代选项,那意味着这是一次更新,我们需要安排一个副作用来执行更新。
const oldProps = current.memoizedProps;
if (oldProps === newProps) {
// In mutation mode, this is sufficient for a bailout because
// we won't touch this node even if children changed.
//
// 在变异模式下,这已经足够进行中止,因为即使子节点发生变化,我们也不会触碰这个节点。
return;
}

markUpdate(workInProgress);
} else if (supportsPersistence) {
const currentInstance = current.stateNode;
const oldProps = current.memoizedProps;
// If there are no effects associated with this node, then none of our children had any updates.
// This guarantees that we can reuse all of them.
//
// 如果这个节点没有关联的副作用,那么我们的子节点就没有任何更新。这保证了我们可以重用它们所有节点。
const requiresClone = doesRequireClone(current, workInProgress);
if (!requiresClone && oldProps === newProps) {
// No changes, just reuse the existing instance.
// Note that this might release a previous clone.
//
// 不做更改,只是重用现有实例。注意,这可能会释放之前的克隆。
workInProgress.stateNode = currentInstance;
return;
}
const currentHostContext = getHostContext();

let newChildSet = null;
let hasOffscreenComponentChild = false;
if (requiresClone && passChildrenWhenCloningPersistedNodes) {
markCloned(workInProgress);
newChildSet = createContainerChildSet();
// If children might have changed, we have to add them all to the set.
// 如果子节点可能已更改,我们必须将它们全部添加到集合中。
//
// function appendAllChildrenToContainer(containerChildSet: ChildSet,
// workInProgress: Fiber, needsVisibilityToggle: boolean,
// isHidden: boolean): boolean
hasOffscreenComponentChild = appendAllChildrenToContainer(
newChildSet,
workInProgress,
false,
false,
);
}

const newInstance = cloneInstance(
currentInstance,
type,
oldProps,
newProps,
!requiresClone,
!hasOffscreenComponentChild ? newChildSet : undefined,
);
if (newInstance === currentInstance) {
// No changes, just reuse the existing instance.
// Note that this might release a previous clone.
//
// 不做更改,只是重用现有实例。注意,这可能会释放之前的克隆。
workInProgress.stateNode = currentInstance;
return;
} else {
markCloned(workInProgress);
}

// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
//
// 某些渲染器在初次挂载时需要提交阶段的副作用。(例如,DOM 渲染器支持对某些元素
// 自动聚焦)。确保这些渲染器的工作被安排在之后执行。
if (
finalizeInitialChildren(newInstance, type, newProps, currentHostContext)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = newInstance;
if (
requiresClone &&
(!passChildrenWhenCloningPersistedNodes || hasOffscreenComponentChild)
) {
// If children have changed, we have to add them all to the set.
// 如果子项发生了变化,我们必须将它们全部添加到集合中。
//
// function appendAllChildren(parent: Instance, workInProgress: Fiber,
// needsVisibilityToggle: boolean, isHidden: boolean): void
appendAllChildren(newInstance, workInProgress, false, false);
}
}
}

9. 预加载实例并在需要时挂起

备注
// This function must be called at the very end of the complete phase, because
// it might throw to suspend, and if the resource immediately loads, the work
// loop will resume rendering as if the work-in-progress completed. So it must
// fully complete.
// TODO: This should ideally move to begin phase, but currently the instance is
// not created until the complete phase. For our existing use cases, host nodes
// that suspend don't have children, so it doesn't matter. But that might not
// always be true in the future.
//
// 这个函数必须在完整阶段的最后调用,因为它可能会抛出以挂起,如果资源立即加载,工作循环将恢复
// 渲染,好像进行中的工作已完成。因此,它必须完全完成。
// TODO: 理想情况下,这应该移动到开始阶段,但目前实例要到完整阶段才创建。对于我们现有的使用
// 情况,挂起的宿主节点没有子节点,所以无所谓。但将来可能并非总是如此。
function preloadInstanceAndSuspendIfNeeded(
workInProgress: Fiber,
type: Type,
oldProps: null | Props,
newProps: Props,
renderLanes: Lanes,
) {
const maySuspend =
(enableSuspenseyImages ||
(workInProgress.mode & SuspenseyImagesMode) !== NoMode) &&
(oldProps === null
? maySuspendCommit(type, newProps)
: maySuspendCommitOnUpdate(type, oldProps, newProps));

if (!maySuspend) {
// If this flag was set previously, we can remove it. The flag
// represents whether this particular set of props might ever need to
// suspend. The safest thing to do is for maySuspendCommit to always
// return true, but if the renderer is reasonably confident that the
// underlying resource won't be evicted, it can return false as a
// performance optimization.
//
// 如果之前设置了这个标志,我们可以将其移除。这个标志表示这组特定的 props 是否可能
// 需要挂起。最安全的做法是让 maySuspendCommit 始终返回 true,但如果渲染器相当
// 有信心底层资源不会被清除,它可以返回 false 以作为性能优化。
workInProgress.flags &= ~MaySuspendCommit;
return;
}

// Mark this fiber with a flag. This gets set on all host instances
// that might possibly suspend, even if they don't need to suspend
// currently. We use this when revealing a prerendered tree, because
// even though the tree has "mounted", its resources might not have
// loaded yet.
//
// 用一个标记标记这个 fiber。所有可能会挂起的宿主实例都会设置这个标记,即使它们
// 当前不需要挂起。我们在显示预渲染的树时会使用它,因为即使树已经“挂载”,它的资源
// 可能尚未加载。
workInProgress.flags |= MaySuspendCommit;

if (
includesOnlySuspenseyCommitEligibleLanes(renderLanes) ||
maySuspendCommitInSyncRender(type, newProps)
) {
// preload the instance if necessary. Even if this is an urgent render there
// could be benefits to preloading early.
// @TODO we should probably do the preload in begin work
//
// 如果有必要,预先加载实例。即使这是紧急渲染,提前预加载也可能有好处。
// @TODO 我们可能应该在开始工作时进行预加载
const isReady = preloadInstance(workInProgress.stateNode, type, newProps);
if (!isReady) {
if (shouldRemainOnPreviousScreen()) {
workInProgress.flags |= ShouldSuspendCommit;
} else {
suspendCommit();
}
} else {
// Even if we're ready we suspend the commit and check again in the pre-commit
// phase if we need to suspend anyway. Such as if it's delayed on decoding or
// if it was dropped from the cache while rendering due to pressure.
//
// 即使我们已经准备好,我们也会暂停提交,并在预提交阶段再次检查是否仍然需要暂停。
// 例如,如果它在解码时被延迟,或者在渲染过程中由于压力从缓存中被丢弃。
workInProgress.flags |= ShouldSuspendCommit;
}
}
}

10. 预加载资源并在需要时挂起

备注
function preloadResourceAndSuspendIfNeeded(
workInProgress: Fiber,
resource: Resource,
type: Type,
props: Props,
renderLanes: Lanes,
) {
// This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.
// 这是 preloadInstanceAndSuspendIfNeeded 的一个分支,但用于资源。
if (!mayResourceSuspendCommit(resource)) {
workInProgress.flags &= ~MaySuspendCommit;
return;
}

workInProgress.flags |= MaySuspendCommit;

const isReady = preloadResource(resource);
if (!isReady) {
if (shouldRemainOnPreviousScreen()) {
workInProgress.flags |= ShouldSuspendCommit;
} else {
suspendCommit();
}
}
}

11. 安排重试效果

备注
function scheduleRetryEffect(
workInProgress: Fiber,
retryQueue: RetryQueue | null,
) {
const wakeables = retryQueue;
if (wakeables !== null) {
// Schedule an effect to attach a retry listener to the promise.
// TODO: Move to passive phase
//
// 安排一个效果,将重试监听器附加到 Promise。
// 待办事项:移到被动阶段
workInProgress.flags |= Update;
}

// Check if we need to schedule an immediate retry. This should happen
// whenever we unwind a suspended tree without fully rendering its siblings;
// we need to begin the retry so we can start prerendering them.
//
// 检查我们是否需要安排立即重试。每当我们展开一个挂起的树而没有完全渲染它的兄弟节点时,都
// 会发生这种情况;我们需要开始重试,以便可以开始对它们进行预渲染。
//
// We also use this mechanism for Suspensey Resources (e.g. stylesheets),
// because those don't actually block the render phase, only the commit phase.
// So we can start rendering even before the resources are ready.
//
// 我们也将这种机制用于 Suspensey 资源(例如样式表),因为这些资源实际上并不会阻塞渲染阶段,只
// 会阻塞提交阶段。因此,即使资源尚未准备好,我们也可以开始渲染。
if (workInProgress.flags & ScheduleRetry) {
const retryLane =
// TODO: This check should probably be moved into claimNextRetryLane
// I also suspect that we need some further consolidation of offscreen
// and retry lanes.
//
// 待办:这个检查可能应该被移到 claimNextRetryLane 中,我也怀疑我们可能需要对屏幕外
// 和重试通道进行进一步整合。
workInProgress.tag !== OffscreenComponent
? claimNextRetryLane()
: OffscreenLane;
workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);

// Track the lanes that have been scheduled for an immediate retry so that
// we can mark them as suspended upon committing the root.
//
// 跟踪已经安排立即重试的通道,以便在提交根时将它们标记为暂停。
markSpawnedRetryLane(retryLane);
}
}

12. 更新宿主环境文本

备注
function updateHostText(
current: Fiber,
workInProgress: Fiber,
oldText: string,
newText: string,
) {
if (supportsMutation) {
// If the text differs, mark it as an update. All the work in done in commitWork.
// 如果文本不同,则将其标记为更新。所有工作都在 commitWork 中完成。
if (oldText !== newText) {
markUpdate(workInProgress);
}
} else if (supportsPersistence) {
if (oldText !== newText) {
// If the text content differs, we'll create a new text instance for it.
// 如果文本内容不同,我们将为它创建一个新的文本实例。
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
markCloned(workInProgress);
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
} else {
workInProgress.stateNode = current.stateNode;
}
}
}

13. cutOffTailIfNeeded

备注
function cutOffTailIfNeeded(
renderState: SuspenseListRenderState,
hasRenderedATailFallback: boolean,
) {
if (getIsHydrating()) {
// If we're hydrating, we should consume as many items as we can
// so we don't leave any behind.
//
// 如果我们在水合,我们应该尽可能多地摄入物品这样就不会遗漏任何东西。
return;
return;
}
switch (renderState.tailMode) {
case 'visible': {
// Everything should remain as it was.
// 一切应保持原样。
break;
}
case 'collapsed': {
// Any insertions at the end of the tail list after this point
// should be invisible. If there are already mounted boundaries
// anything before them are not considered for collapsing.
// Therefore we need to go through the whole tail to find if
// there are any.
//
// 在此之后对尾部列表的任何插入都应该是不可见的。如果已有挂载的边界,那些边界
// 之前的内容不会被考虑合并。因此我们需要遍历整个尾部列表以查找是否存在任何内容。
let tailNode = renderState.tail;
let lastTailNode = null;
while (tailNode !== null) {
if (tailNode.alternate !== null) {
lastTailNode = tailNode;
}
tailNode = tailNode.sibling;
}
// Next we're simply going to delete all insertions after the
// last rendered item.
//
// 接下来我们只需删除最后一个渲染项之后的所有插入内容。
if (lastTailNode === null) {
// All remaining items in the tail are insertions.
// 尾部的所有剩余项都是插入项。
if (!hasRenderedATailFallback && renderState.tail !== null) {
// We suspended during the head. We want to show at least one
// row at the tail. So we'll keep on and cut off the rest.
//
// 我们在头部暂停。我们希望至少显示尾部的一行。 所以我们会继续并截断其余部分。
renderState.tail.sibling = null;
} else {
renderState.tail = null;
}
} else {
// Detach the insertion after the last node that was already
// inserted.
//
// 在已经插入的最后一个节点之后分离插入内容。
lastTailNode.sibling = null;
}
break;
}
// Hidden is now the default.
// 默认现在是隐藏。
case 'hidden':
default: {
// Any insertions at the end of the tail list after this point
// should be invisible. If there are already mounted boundaries
// anything before them are not considered for collapsing.
// Therefore we need to go through the whole tail to find if
// there are any.
//
// 在此之后在尾部列表末尾的任何插入都应该是不可见的。如果已经存在已挂载的边界,
// 它们之前的任何内容都不被视为可以折叠的。因此我们需要遍历整个尾部列表,以
// 查看是否存在任何边界。
let tailNode = renderState.tail;
let lastTailNode = null;
while (tailNode !== null) {
if (tailNode.alternate !== null) {
lastTailNode = tailNode;
}
tailNode = tailNode.sibling;
}
// Next we're simply going to delete all insertions after the
// last rendered item.
//
// 接下来我们只需删除最后一个渲染项之后的所有插入内容。
if (lastTailNode === null) {
// All remaining items in the tail are insertions.
// 尾部的所有剩余项都是插入项。
renderState.tail = null;
} else {
// Detach the insertion after the last node that was already
// inserted.
// 在已经插入的最后一个节点之后分离插入内容。
lastTailNode.sibling = null;
}
break;
}
}
}

14. 判定是否仅新挂载

function isOnlyNewMounts(tail: Fiber): boolean {
let fiber: null | Fiber = tail;
while (fiber !== null) {
if (fiber.alternate !== null) {
return false;
}
fiber = fiber.sibling;
}
return true;
}

15. 冒泡属性

备注
function bubbleProperties(completedWork: Fiber) {
const didBailout =
completedWork.alternate !== null &&
completedWork.alternate.child === completedWork.child;

let newChildLanes: Lanes = NoLanes;
let subtreeFlags: Flags = NoFlags;

if (!didBailout) {
// Bubble up the earliest expiration time.
// 冒泡出最早的过期时间。
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
// In profiling mode, resetChildExpirationTime is also used to reset
// profiler durations.
//
// 在分析模式下,resetChildExpirationTime 也用于重置分析器的持续时间。
let actualDuration = completedWork.actualDuration;
let treeBaseDuration = completedWork.selfBaseDuration as any as number;

let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;

// When a fiber is cloned, its actualDuration is reset to 0. This value will
// only be updated if work is done on the fiber (i.e. it doesn't bailout).
// When work is done, it should bubble to the parent's actualDuration. If
// the fiber has not been cloned though, (meaning no work was done), then
// this value will reflect the amount of time spent working on a previous
// render. In that case it should not bubble. We determine whether it was
// cloned by comparing the child pointer.
//
// 当一个 fiber 被克隆时,它的 actualDuration 会被重置为 0。这个值只有在 fiber 上
// 进行了工作时才会更新(即它没有中止)。
// 当工作完成时,它的值应该向父节点的 actualDuration 冒泡传递。如果 fiber 没有被克隆
// (意味着没有进行任何工作),那么这个值将反映之前渲染所花费的时间。在这种情况下,它不应
// 该冒泡。我们通过比较子指针来判断它是否被克隆。

actualDuration += child.actualDuration;
treeBaseDuration += child.treeBaseDuration;
child = child.sibling;
}

completedWork.actualDuration = actualDuration;
completedWork.treeBaseDuration = treeBaseDuration;
} else {
let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;

// Update the return pointer so the tree is consistent. This is a code
// smell because it assumes the commit phase is never concurrent with
// the render phase. Will address during refactor to alternate model.
//
// 更新返回指针以保持树的一致性。这是一个代码异味,因为它假设提交阶段永远不会与渲染
// 阶段并发。将在重构为交替模型时处理。
child.return = completedWork;

child = child.sibling;
}
}

completedWork.subtreeFlags |= subtreeFlags;
} else {
// Bubble up the earliest expiration time.
// 冒泡出最早的过期时间。
if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
// In profiling mode, resetChildExpirationTime is also used to reset
// profiler durations.
//
// 在分析模式下,resetChildExpirationTime 也用于重置分析器的持续时间。
let treeBaseDuration = completedWork.selfBaseDuration as any as number;

let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

// "Static" flags share the lifetime of the fiber/hook they belong to,
// so we should bubble those up even during a bailout. All the other
// flags have a lifetime only of a single render + commit, so we should
// ignore them.
//
// “静态”标记共享它们所属 fiber/hook 的生命周期,因此即使在放弃时,我们也应该将它们向
// 上传递。其他所有标记的生命周期仅限于单次渲染提交,因此我们应该忽略它们。
subtreeFlags |= child.subtreeFlags & StaticMask;
subtreeFlags |= child.flags & StaticMask;
treeBaseDuration += child.treeBaseDuration;
child = child.sibling;
}

completedWork.treeBaseDuration = treeBaseDuration;
} else {
let child = completedWork.child;
while (child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(child.lanes, child.childLanes),
);

// "Static" flags share the lifetime of the fiber/hook they belong to,
// so we should bubble those up even during a bailout. All the other
// flags have a lifetime only of a single render + commit, so we should
// ignore them.
//
// “静态”标记共享它们所属 fiber/hook 的生命周期,因此即使在放弃时,我们也应该将它们
// 向上传递。其他所有标记的生命周期仅限于单次渲染提交,因此我们应该忽略它们。
subtreeFlags |= child.subtreeFlags & StaticMask;
subtreeFlags |= child.flags & StaticMask;

// Update the return pointer so the tree is consistent. This is a code
// smell because it assumes the commit phase is never concurrent with
// the render phase. Will address during refactor to alternate model.
//
// 更新返回指针以保持树的一致性。这是一个代码异味,因为它假设提交阶段永远不会与渲染阶段
// 并发。在重构为替代理模型时会处理。
child.return = completedWork;

child = child.sibling;
}
}

completedWork.subtreeFlags |= subtreeFlags;
}

completedWork.childLanes = newChildLanes;

return didBailout;
}

16. 完全脱水活动边界

备注
function completeDehydratedActivityBoundary(
current: Fiber | null,
workInProgress: Fiber,
nextState: ActivityState | null,
): boolean {
const wasHydrated = popHydrationState(workInProgress);

if (nextState !== null) {
// We might be inside a hydration state the first time we're picking up this
// Activity boundary, and also after we've reentered it for further hydration.
//
// 当我们第一次接收到这个活动边界时,可能处于一个 hydration 状态,也可能在重新进入它
// 进行进一步 hydration 之后。
if (current === null) {
if (!wasHydrated) {
throw new Error(
'A dehydrated suspense component was completed without a hydrated node. ' +
'This is probably a bug in React.',
);
}
prepareToHydrateHostActivityInstance(workInProgress);
bubbleProperties(workInProgress);
if (enableProfilerTimer) {
if ((workInProgress.mode & ProfileMode) !== NoMode) {
const isTimedOutSuspense = nextState !== null;
if (isTimedOutSuspense) {
// Don't count time spent in a timed out Suspense subtree as part of
// the base duration.
//
// 不要将超时的 Suspense 子树中花费的时间计入基本持续时间。
const primaryChildFragment = workInProgress.child;
if (primaryChildFragment !== null) {
workInProgress.treeBaseDuration -=
primaryChildFragment.treeBaseDuration as any as number;
}
}
}
}
return false;
} else {
emitPendingHydrationWarnings();
// We might have reentered this boundary to hydrate it. If so, we need to
// reset the hydration state since we're now exiting out of it.
// popHydrationState doesn't do that for us.
//
// 我们可能重新进入了这个边界来进行 hydration。如果是这样,我们需要重置 hydration 状态,
// 因为我们现在正要退出它。popHydrationState 不会为我们执行此操作。
resetHydrationState();
if ((workInProgress.flags & DidCapture) === NoFlags) {
// This boundary did not suspend so it's now hydrated and unsuspended.
// 这个边界没有被挂起,所以现在已经被注水并且未被挂起。
nextState = workInProgress.memoizedState = null;
}
// If nothing suspended, we need to schedule an effect to mark this boundary
// as having hydrated so events know that they're free to be invoked.
// It's also a signal to replay events and the suspense callback.
// If something suspended, schedule an effect to attach retry listeners.
// So we might as well always mark this.
//
// 如果没有任何挂起,我们需要安排一个 effect 来标记这个边界表示已经完成 hydration,以
// 便事件知道它们可以被触发。这也是重放事件和 suspense 回调的一个信号。如果有挂起的情况,
// 则安排一个 effect 来附加重试监听器。因此,我们不妨总是进行标记。
workInProgress.flags |= Update;
bubbleProperties(workInProgress);
if (enableProfilerTimer) {
if ((workInProgress.mode & ProfileMode) !== NoMode) {
const isTimedOutSuspense = nextState !== null;
if (isTimedOutSuspense) {
// Don't count time spent in a timed out Suspense subtree as part
// of the base duration.
//
// 不要将超时的 Suspense 子树中花费的时间计入基础持续时间。
const primaryChildFragment = workInProgress.child;
if (primaryChildFragment !== null) {
workInProgress.treeBaseDuration -=
primaryChildFragment.treeBaseDuration as any as number;
}
}
}
}
return false;
}
} else {
// Successfully completed this tree. If this was a forced client render,
// there may have been recoverable errors during first hydration
// attempt. If so, add them to a queue so we can log them in the
// commit phase. We also add them to prev state so we can get to them
// from the Suspense Boundary.
//
// 成功完成了这棵树。如果这是一次强制的客户端渲染,在第一次 hydration 尝试期间可能出现了
// 可恢复的错误。如果是这样,将它们添加到队列中,以便我们可以在提交阶段记录它们。我们还将
// 它们添加到前一个状态中,以便可以从 Suspense 边界访问它们。
const hydrationErrors = upgradeHydrationErrorsToRecoverable();
if (current !== null && current.memoizedState !== null) {
const prevState: ActivityState = current.memoizedState;
prevState.hydrationErrors = hydrationErrors;
}
// Fall through to normal Offscreen path
// 跌入正常的离屏路径
return true;
}
}

17. 完全脱水挂起边界

备注
function completeDehydratedSuspenseBoundary(
current: Fiber | null,
workInProgress: Fiber,
nextState: SuspenseState | null,
): boolean {
const wasHydrated = popHydrationState(workInProgress);

if (nextState !== null && nextState.dehydrated !== null) {
// We might be inside a hydration state the first time we're picking up this
// Suspense boundary, and also after we've reentered it for further hydration.
//
// 当我们第一次拾取这个 Suspense 边界时,或者在为了进一步 hydration 而重新进入它
// 之后,我们可能处于 hydration 状态。
if (current === null) {
if (!wasHydrated) {
throw new Error(
'A dehydrated suspense component was completed without a hydrated node. ' +
'This is probably a bug in React.',
);
}
prepareToHydrateHostSuspenseInstance(workInProgress);
bubbleProperties(workInProgress);
if (enableProfilerTimer) {
if ((workInProgress.mode & ProfileMode) !== NoMode) {
const isTimedOutSuspense = nextState !== null;
if (isTimedOutSuspense) {
// Don't count time spent in a timed out Suspense subtree as
// part of the base duration.
//
// 不要将超时的 Suspense 子树中花费的时间计入基本持续时间。
const primaryChildFragment = workInProgress.child;
if (primaryChildFragment !== null) {
workInProgress.treeBaseDuration -=
primaryChildFragment.treeBaseDuration as any as number;
}
}
}
}
return false;
} else {
emitPendingHydrationWarnings();
// We might have reentered this boundary to hydrate it. If so, we need to
// reset the hydration state since we're now exiting out of it.
// popHydrationState doesn't do that for us.
//
// 我们可能重新进入了这个边界以进行 hydration。如果是这样,我们需要重置 hydration 状态,
// 因为我们现在正要退出它。popHydrationState 不会为我们做到这一点。
resetHydrationState();
if ((workInProgress.flags & DidCapture) === NoFlags) {
// This boundary did not suspend so it's now hydrated and unsuspended.
// 这个边界没有被挂起,所以现在已经被注水并且未被挂起。
nextState = workInProgress.memoizedState = null;
}
// If nothing suspended, we need to schedule an effect to mark this boundary
// as having hydrated so events know that they're free to be invoked.
// It's also a signal to replay events and the suspense callback.
// If something suspended, schedule an effect to attach retry listeners.
// So we might as well always mark this.
//
// 如果没有任何挂起,我们需要安排一个 effect 来标记这个边界已完成 hydration,以便事件知道可以触发。
// 这也是一个信号,用于重放事件和 suspense 回调。如果有挂起,则安排一个 effect 来附加重试监听器。
// 所以我们不妨总是标记它。
workInProgress.flags |= Update;
bubbleProperties(workInProgress);
if (enableProfilerTimer) {
if ((workInProgress.mode & ProfileMode) !== NoMode) {
const isTimedOutSuspense = nextState !== null;
if (isTimedOutSuspense) {
// Don't count time spent in a timed out Suspense subtree
// as part of the base duration.
//
// 不要将超时的 Suspense 子树中花费的时间计入基本持续时间。
const primaryChildFragment = workInProgress.child;
if (primaryChildFragment !== null) {
workInProgress.treeBaseDuration -=
primaryChildFragment.treeBaseDuration as any as number;
}
}
}
}
return false;
}
} else {
// Successfully completed this tree. If this was a forced client render,
// there may have been recoverable errors during first hydration
// attempt. If so, add them to a queue so we can log them in the
// commit phase. We also add them to prev state so we can get to them
// from the Suspense Boundary.
//
// 成功完成了这个树。如果这是强制的客户端渲染,在第一次 hydration 尝试期间可能出现了
// 可恢复的错误。如果是这样,将它们添加到队列中,以便我们可以在提交阶段记录它们。
// 我们还将它们添加到之前的状态中,以便可以从 Suspense 边界访问它们。
const hydrationErrors = upgradeHydrationErrorsToRecoverable();
if (current !== null && current.memoizedState !== null) {
const prevState: SuspenseState = current.memoizedState;
prevState.hydrationErrors = hydrationErrors;
}
// Fall through to normal Suspense path
// 跌入正常的 Suspense 路径
return true;
}
}