React Fiber 挂起上下文
一、作用
二、获取壳边界
export function getShellBoundary(): Fiber | null {
return shellBoundary;
}
三、推送主树挂起处理器
备注
push()由 ReactFiberStack#push 实现pop()由 ReactFiberStack#pop 实现isCurrentTreeHidden()由 ReactFiberHiddenContext#isCurrentTreeHidden 实现
export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void {
// TODO: Pass as argument
// 待办:作为参数传递
const current = handler.alternate;
const props: SuspenseProps = handler.pendingProps;
// Shallow Suspense context fields, like ForceSuspenseFallback, should only be
// propagated a single level. For example, when ForceSuspenseFallback is set,
// it should only force the nearest Suspense boundary into fallback mode.
//
// 浅层 Suspense 上下文字段,如 ForceSuspenseFallback,只应传播到单一层级。例如,当
// 设置了 ForceSuspenseFallback 时,它只应强制最近的 Suspense 边界进入回退模式。
push(
suspenseStackCursor,
setDefaultShallowSuspenseListContext(suspenseStackCursor.current),
handler,
);
// Experimental feature: Some Suspense boundaries are marked as having an
// undesirable fallback state. These have special behavior where we only
// activate the fallback if there's no other boundary on the stack that we can
// use instead.
//
// 实验性功能:一些 Suspense 边界被标记为具有不理想的回退状态。它们有特殊的行为,只有当堆栈
// 上没有其他可用的边界时,我们才会激活回退。
if (
enableSuspenseAvoidThisFallback &&
props.unstable_avoidThisFallback === true &&
// If an avoided boundary is already visible, it behaves identically to
// a regular Suspense boundary.
//
// 如果已避开的边界已经可见,它的行为与常规 Suspense 边界完全相同。
(current === null || isCurrentTreeHidden())
) {
if (shellBoundary === null) {
// We're rendering in the shell. There's no parent Suspense boundary that
// can provide a desirable fallback state. We'll use this boundary.
//
// 我们正在在宿主环境中渲染。没有父级 Suspense 边界可以提供理想的回退状态。
// 我们将使用这个边界。
push(suspenseHandlerStackCursor, handler, handler);
// However, because this is not a desirable fallback, the children are
// still considered part of the shell. So we intentionally don't assign
// to `shellBoundary`.
//
// 然而,由于这不是一个理想的回退方式,这些子元素仍然被视为外壳的一部分。所以我们有意
// 不将它们分配给 `shellBoundary`。
} else {
// There's already a parent Suspense boundary that can provide a desirable
// fallback state. Prefer that one.
//
// 已经有一个父级 Suspense 边界可以提供期望的回退状态。优先使用它。
const handlerOnStack = suspenseHandlerStackCursor.current;
push(suspenseHandlerStackCursor, handlerOnStack, handler);
}
return;
}
// TODO: If the parent Suspense handler already suspended, there's no reason
// to push a nested Suspense handler, because it will get replaced by the
// outer fallback, anyway. Consider this as a future optimization.
//
// 待办:如果父级 Suspense 处理器已经挂起,就没有必要再添加一个嵌套的 Suspense 处理器,因
// 为它无论如何都会被外部的回退替换。将此视为未来的优化方向。
push(suspenseHandlerStackCursor, handler, handler);
if (shellBoundary === null) {
if (current === null || isCurrentTreeHidden()) {
// This boundary is not visible in the current UI.
// 当前界面中该边界不可见。
shellBoundary = handler;
} else {
const prevState: SuspenseState = current.memoizedState;
if (prevState !== null) {
// This boundary is showing a fallback in the current UI.
// 当前界面中此边界显示为回退
shellBoundary = handler;
}
}
}
}
四、 设置默认浅层挂起列表上下文
export function setDefaultShallowSuspenseListContext(
parentContext: SuspenseContext,
): SuspenseContext {
return parentContext & SubtreeSuspenseContextMask;
}
五、推送备用树挂起处理器
export function pushFallbackTreeSuspenseHandler(fiber: Fiber): void {
// We're about to render the fallback. If something in the fallback suspends,
// it's akin to throwing inside of a `catch` block. This boundary should not
// capture. Reuse the existing handler on the stack.
//
// 我们即将渲染备用内容。如果备用内容中的某些内容挂起,
// 这类似于在 `catch` 块内抛出异常。此边界不应捕获。
// 重用堆栈上现有的处理程序。
reuseSuspenseHandlerOnStack(fiber);
}
六、在堆栈上重用 Suspense 处理程序
备注
push()由 ReactFiberStack#push 实现
export function reuseSuspenseHandlerOnStack(fiber: Fiber) {
push(suspenseStackCursor, suspenseStackCursor.current, fiber);
push(suspenseHandlerStackCursor, getSuspenseHandler(), fiber);
}
七、获取挂起处理器
export function getSuspenseHandler(): Fiber | null {
return suspenseHandlerStackCursor.current;
}
八、推送脱水活动挂起处理器
备注
push()由 ReactFiberStack#push 实现
export function pushDehydratedActivitySuspenseHandler(fiber: Fiber): void {
// This is called when hydrating an Activity boundary. We can just leave it
// dehydrated if it suspends.
// A SuspenseList context is only pushed here to avoid a push/pop mismatch.
// Reuse the current value on the stack.
// TODO: We can avoid needing to push here by by forking popSuspenseHandler
// into separate functions for Activity, Suspense and Offscreen.
//
// 当给 Activity 边界进行 hydration 时会调用这个函数。如果它挂起,我们可以保持它处于脱水状态。
// 这里推入一个 SuspenseList 上下文只是为了避免 push/pop 不匹配。
// 重用栈上的当前值。
// TODO:我们可以通过将 popSuspenseHandler 拆分为 Activity、Suspense 和
// Offscreen 的单独函数,来避免在这里进行推入。
push(suspenseStackCursor, suspenseStackCursor.current, fiber);
push(suspenseHandlerStackCursor, fiber, fiber);
if (shellBoundary === null) {
// We can contain any suspense inside the Activity boundary.
// 我们可以将任何挂起限制在 Activity 边界内。
shellBoundary = fiber;
}
}
九、推送屏幕外的挂起处理器
备注
push()由 ReactFiberStack#push 实现
export function pushOffscreenSuspenseHandler(fiber: Fiber): void {
if (fiber.tag === OffscreenComponent) {
// A SuspenseList context is only pushed here to avoid a push/pop mismatch.
// Reuse the current value on the stack.
// TODO: We can avoid needing to push here by by forking popSuspenseHandler
// into separate functions for Activity, Suspense and Offscreen.
//
// 这里仅推送一个 SuspenseList 上下文以避免 push/pop 不匹配。
// 重用栈上的当前值。
// TODO: 我们可以通过将 popSuspenseHandler 拆分为 Activity、Suspense 和
// Offscreen 的单独函数来避免在这里推送。
push(suspenseStackCursor, suspenseStackCursor.current, fiber);
push(suspenseHandlerStackCursor, fiber, fiber);
if (shellBoundary === null) {
// We're rendering hidden content. If it suspends, we can handle it by
// just not committing the offscreen boundary.
//
// 我们正在渲染隐藏内容。如果它被挂起,我们可以通过不提交屏幕外边界来处理它。
shellBoundary = fiber;
}
} else {
// This is a LegacyHidden component.
// 这是一个 LegacyHidden 组件。
reuseSuspenseHandlerOnStack(fiber);
}
}
十、弹出挂起处理程序
备注
pop()由 ReactFiberStack#pop 实现
export function popSuspenseHandler(fiber: Fiber): void {
pop(suspenseHandlerStackCursor, fiber);
if (shellBoundary === fiber) {
// Popping back into the shell.
// 弹回到外壳里。
shellBoundary = null;
}
pop(suspenseStackCursor, fiber);
}
十一、导出的常量
1. 强制使用备用方案
备注
源码中 173 - 175 行
// ForceSuspenseFallback can be used by SuspenseList to force newly added
// items into their fallback state during one of the render passes.
//
// ForceSuspenseFallback 可以被 SuspenseList 用来在某次渲染过程中强制
// 将新添加的项置于回退状态。
export const ForceSuspenseFallback: ShallowSuspenseContext = 0b10;
2. 挂起栈指针
备注
createCursor()由 ReactFiberStack#createCursor 实现
在源码的 177 行
export const suspenseStackCursor: StackCursor<SuspenseContext> = createCursor(
DefaultSuspenseContext,
);
十二、设置浅层挂起列表上下文
export function setShallowSuspenseListContext(
parentContext: SuspenseContext,
shallowContext: ShallowSuspenseContext,
): SuspenseContext {
return (parentContext & SubtreeSuspenseContextMask) | shallowContext;
}
十三、推送挂起列表上下文
备注
push()由 ReactFiberStack#push 实现
export function pushSuspenseListContext(
fiber: Fiber,
newContext: SuspenseContext,
): void {
// Push the current handler in this case since we're not catching at the SuspenseList
// for typical rows.
//
// 在这种情况下推送当前处理程序,因为我们没有在 SuspenseList 中捕获典型行。
const handlerOnStack = suspenseHandlerStackCursor.current;
push(suspenseHandlerStackCursor, handlerOnStack, fiber);
push(suspenseStackCursor, newContext, fiber);
}
十四、推送挂起列表捕获
备注
push()由 ReactFiberStack#push 实现
export function pushSuspenseListCatch(
fiber: Fiber,
newContext: SuspenseContext,
): void {
// In this case we do want to handle catching suspending on the actual boundary itself.
// This is used for rows that are allowed to be hidden anyway.
//
// 在这种情况下,我们确实希望处理在实际边界上挂起的情况。
// 这用于那些本来就允许被隐藏的行。
push(suspenseHandlerStackCursor, fiber, fiber);
push(suspenseStackCursor, newContext, fiber);
if (shellBoundary === null) {
// We can contain the effects to hiding the current row.
// 我们可以将影响限制在隐藏当前行
shellBoundary = fiber;
}
}
十五、弹出挂起列表上下文
export function popSuspenseListContext(fiber: Fiber): void {
pop(suspenseStackCursor, fiber);
pop(suspenseHandlerStackCursor, fiber);
if (shellBoundary === fiber) {
// Popping back into the shell.
// 弹回到外壳里。
shellBoundary = null;
}
}
十六、有挂起列表上下文
export function hasSuspenseListContext(
parentContext: SuspenseContext,
flag: SuspenseContext,
): boolean {
return (parentContext & flag) !== 0;
}
十七、常量
1. 挂起处理堆栈指针
备注
createCursor()由 ReactFiberStack#createCursor 实现
// The Suspense handler is the boundary that should capture if something
// suspends, i.e. it's the nearest `catch` block on the stack.
//
// Suspense 处理程序是应该捕获挂起情况的边界,
// 也就是说,它是堆栈上最近的 `catch` 块。
const suspenseHandlerStackCursor: StackCursor<Fiber | null> =
createCursor(null);
2. 默认挂起上下文
备注
源码中 169 行
const DefaultSuspenseContext: SuspenseContext = 0b00;
3. 子树挂起上下文掩码
备注
源码中 171 行
const SubtreeSuspenseContextMask: SuspenseContext = 0b01;
十八、变量
1. 壳边界
// Represents the outermost boundary that is not visible in the current tree.
// Everything above this is the "shell". When this is null, it means we're
// rendering in the shell of the app. If it's non-null, it means we're rendering
// deeper than the shell, inside a new tree that wasn't already visible.
//
// 表示当前树中不可见的最外层边界。
// 这一层以上的部分是“外壳”。当它为 null 时,意味着我们正在在应用的外壳中渲染。如果它
// 非 null,则意味着我们正在外壳之下渲染,在一个新树中,这个树之前不可见。
//
// The main way we use this concept is to determine whether showing a fallback
// would result in a desirable or undesirable loading state. Activing a fallback
// in the shell is considered an undersirable loading state, because it would
// mean hiding visible (albeit stale) content in the current tree — we prefer to
// show the stale content, rather than switch to a fallback. But showing a
// fallback in a new tree is fine, because there's no stale content to
// prefer instead.
//
// 我们使用这个概念的主要方式是确定显示回退是否会导致理想或不理想的加载状态。在外壳中启用回退
// 被认为是不理想的加载状态,因为这意味着会隐藏当前树中可见的(尽管已经过时的)内容——我们更倾向
// 于显示过时内容,而不是切换到回退。然而,在新树中显示回退是可以的,因为没有可替代的过时内容。
let shellBoundary: Fiber | null = null;