React Fiber 抛出(错误)
一、作用
二、创建根错误更新
备注
createUpdate()由 ReactFiberClassUpdateQueue#createUpdate 实现runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现logUncaughtError()由 ReactFiberErrorLogger#logUncaughtError 实现
function createRootErrorUpdate(
root: FiberRoot,
errorInfo: CapturedValue<mixed>,
lane: Lane,
): Update<mixed> {
const update = createUpdate(lane);
// Unmount the root by rendering null.
// 通过渲染 null 卸载根节点。
update.tag = CaptureUpdate;
// Caution: React DevTools currently depends on this property
// being called "element".
//
// 注意:React 开发者工具目前依赖于该属性被称为“element”。
update.payload = { element: null };
update.callback = () => {
if (__DEV__) {
runWithFiberInDEV(errorInfo.source, logUncaughtError, root, errorInfo);
} else {
logUncaughtError(root, errorInfo);
}
};
return update;
}
三、创建类错误更新
备注
createUpdate()由 ReactFiberClassUpdateQueue#createUpdate 实现
function createClassErrorUpdate(lane: Lane): Update<mixed> {
const update = createUpdate(lane);
update.tag = CaptureUpdate;
return update;
}
四、初始化类错误更新
备注
logCaughtError()由 ReactFiberErrorLogger#logCaughtError 实现runWithFiberInDEV()由 ReactCurrentFiber#runWithFiberInDEV 实现markLegacyErrorBoundaryAsFailed()由 ReactFiberWorkLoop 提供callComponentDidCatchInDEV()由 ReactFiberCallUserSpace#callComponentDidCatchInDEV 实现getComponentNameFromFiber()由 getComponentNameFromFiber 实现
function initializeClassErrorUpdate(
update: Update<mixed>,
root: FiberRoot,
fiber: Fiber,
errorInfo: CapturedValue<mixed>,
): void {
const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
if (typeof getDerivedStateFromError === 'function') {
const error = errorInfo.value;
update.payload = () => {
return getDerivedStateFromError(error);
};
update.callback = () => {
if (__DEV__) {
markFailedErrorBoundaryForHotReloading(fiber);
}
if (__DEV__) {
runWithFiberInDEV(
errorInfo.source,
logCaughtError,
root,
fiber,
errorInfo,
);
} else {
logCaughtError(root, fiber, errorInfo);
}
};
}
const inst = fiber.stateNode;
if (inst !== null && typeof inst.componentDidCatch === 'function') {
update.callback = function callback() {
if (__DEV__) {
markFailedErrorBoundaryForHotReloading(fiber);
}
if (__DEV__) {
runWithFiberInDEV(
errorInfo.source,
logCaughtError,
root,
fiber,
errorInfo,
);
} else {
logCaughtError(root, fiber, errorInfo);
}
if (typeof getDerivedStateFromError !== 'function') {
// To preserve the preexisting retry behavior of error boundaries,
// we keep track of which ones already failed during this batch.
// This gets reset before we yield back to the browser.
// TODO: Warn in strict mode if getDerivedStateFromError is
// not defined.
//
// 为了保留错误边界已有的重试行为,
// 我们会跟踪在这一批次中哪些已经失败。
// 在我们将控制权交还给浏览器之前,这会被重置。
// TODO: 如果未定义 getDerivedStateFromError,在严格模式下发出警告。
markLegacyErrorBoundaryAsFailed(this);
}
if (__DEV__) {
callComponentDidCatchInDEV(this, errorInfo);
} else {
const error = errorInfo.value;
const stack = errorInfo.stack;
this.componentDidCatch(error, {
componentStack: stack !== null ? stack : '',
});
}
if (__DEV__) {
if (typeof getDerivedStateFromError !== 'function') {
// If componentDidCatch is the only error boundary method defined,
// then it needs to call setState to recover from errors.
// If no state update is scheduled then the boundary will swallow the error.
//
// 如果 componentDidCatch 是唯一定义的错误边界方法,
// 那么它需要调用 setState 来从错误中恢复。
// 如果没有计划状态更新,则边界将忽略错误。
if (!includesSomeLane(fiber.lanes, SyncLane as Lane)) {
console.error(
'%s: Error boundaries should implement getDerivedStateFromError(). ' +
'In that method, return a state update to display an error message or fallback UI.',
getComponentNameFromFiber(fiber) || 'Unknown',
);
}
}
}
};
}
}
五、抛出异常
备注
restorePendingUpdaters()由 ReactFiberWorkLoop 提供getIsHydrating()由 ReactFiberHydrationContext#getIsHydrating 实现markDidThrowWhileHydratingDEV()由 ReactFiberHydrationContext#markDidThrowWhileHydratingDEV 实现getSuspenseHandler()由 ReactFiberSuspenseContext#getSuspenseHandler 实现getShellBoundary()由 ReactFiberSuspenseContext#getShellBoundary 实现renderDidSuspendDelayIfPossible()由 ReactFiberWorkLoop 提供renderDidSuspend()由 ReactFiberWorkLoop 提供attachPingListener()由 ReactFiberWorkLoop 提供queueHydrationError()由 ReactFiberHydrationContext#queueHydrationError 实现createCapturedValueAtFiber()由 ReactCapturedValue#createCapturedValueAtFiber 实现pickArbitraryLane()由 ReactFiberLane#pickArbitraryLane 实现mergeLanes()由 ReactFiberLane#mergeLanes 实现enqueueCapturedUpdate()由 ReactFiberClassUpdateQueue#enqueueCapturedUpdate 实现renderDidError()由 ReactFiberWorkLoop 提供isAlreadyFailedLegacyErrorBoundary()由 ReactFiberWorkLoop 提供
function throwException(
root: FiberRoot,
returnFiber: Fiber | null,
sourceFiber: Fiber,
value: mixed,
rootRenderLanes: Lanes,
): boolean {
// The source fiber did not complete.
// 源 fiber 未完成。
sourceFiber.flags |= Incomplete;
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
// If we have pending work still, restore the original updaters
// 这是一个可唤醒对象。组件已挂起。
restorePendingUpdaters(root, rootRenderLanes);
}
}
if (value !== null && typeof value === 'object') {
if (typeof value.then === 'function') {
// This is a wakeable. The component suspended.
const wakeable: Wakeable = value as any;
resetSuspendedComponent(sourceFiber, rootRenderLanes);
if (__DEV__) {
if (
getIsHydrating() &&
(disableLegacyMode || sourceFiber.mode & ConcurrentMode)
) {
markDidThrowWhileHydratingDEV();
}
}
// Mark the nearest Suspense boundary to switch to rendering a fallback.
// 标记最近的 Suspense 边界以切换到渲染备用内容。
const suspenseBoundary = getSuspenseHandler();
if (suspenseBoundary !== null) {
switch (suspenseBoundary.tag) {
case ActivityComponent:
case SuspenseComponent:
case SuspenseListComponent: {
// If this suspense/activity boundary is not already showing a fallback, mark
// the in-progress render as suspended. We try to perform this logic
// as soon as soon as possible during the render phase, so the work
// loop can know things like whether it's OK to switch to other tasks,
// or whether it can wait for data to resolve before continuing.
// TODO: Most of these checks are already performed when entering a
// Suspense boundary. We should track the information on the stack so
// we don't have to recompute it on demand. This would also allow us
// to unify with `use` which needs to perform this logic even sooner,
// before `throwException` is called.
//
// 如果这个 Suspense/活动边界尚未显示回退内容,则将进行中的渲染标记为挂起。我们会尽可能在渲染阶段尽早执行此逻辑,以便工作循环可以知道例如是否可以切换到其他任务,或者是否可以等待数据解析后再继续。
// TODO:大多数这些检查在进入 Suspense 边界时已经执行。我们应该在堆栈上跟踪这些信息,这样就不必按需重新计算。这也将允许我们与 `use` 统一,它需要更早执行此逻辑,在 `throwException` 被调用之前。
if (disableLegacyMode || sourceFiber.mode & ConcurrentMode) {
if (getShellBoundary() === null) {
// Suspended in the "shell" of the app. This is an undesirable
// loading state. We should avoid committing this tree.
//
// 挂起在应用的“壳”中。这是一个不理想的加载状态。
// 我们应该避免提交这个树。
renderDidSuspendDelayIfPossible();
} else {
// If we suspended deeper than the shell, we don't need to delay
// the commmit. However, we still call renderDidSuspend if this is
// a new boundary, to tell the work loop that a new fallback has
// appeared during this render.
// TODO: Theoretically we should be able to delete this branch.
// It's currently used for two things: 1) to throttle the
// appearance of successive loading states, and 2) in
// SuspenseList, to determine whether the children include any
// pending fallbacks. For 1, we should apply throttling to all
// retries, not just ones that render an additional fallback. For
// 2, we should check subtreeFlags instead. Then we can delete
// this branch.
//
// 如果我们挂起的深度超过了 shell,我们就不需要延迟提交。
// 但是,如果这是一个新的边界,我们仍然会调用 renderDidSuspend,
// 以告诉工作循环在此渲染过程中出现了新的回退。
// TODO: 理论上我们应该可以删除这个分支。
// 目前它用于两件事:1) 控制连续加载状态的出现,
// 2) 在 SuspenseList 中,用于判断子节点是否包含任何待处理回退。
// 对于第 1 点,我们应该将节流应用于所有重试,而不仅仅是渲染额外回退的重试。
// 对于第 2 点,我们应该改为检查 subtreeFlags。然后我们就可以删除这个分支。
const current = suspenseBoundary.alternate;
if (current === null) {
renderDidSuspend();
}
}
}
suspenseBoundary.flags &= ~ForceClientRender;
markSuspenseBoundaryShouldCapture(
suspenseBoundary,
returnFiber,
sourceFiber,
root,
rootRenderLanes,
);
// Retry listener
// 重试监听器
//
// If the fallback does commit, we need to attach a different type of
// listener. This one schedules an update on the Suspense boundary to
// turn the fallback state off.
//
// 如果回退提交,我们需要附加一种不同类型的
// 监听器。这个监听器会在 Suspense 边界上调度一次更新,
// 以关闭回退状态。
//
// Stash the wakeable on the boundary fiber so we can access it in the
// commit phase.
//
// 将可唤醒对象存放在边界 fiber 上,以便我们在提交阶段访问它。
//
// When the wakeable resolves, we'll attempt to render the boundary
// again ("retry").
//
// 当可唤醒对象解决时,我们将尝试再次呈现边界(“重试”)。
// Check if this is a Suspensey resource. We do not attach retry
// listeners to these, because we don't actually need them for
// rendering. Only for committing. Instead, if a fallback commits
// and the only thing that suspended was a Suspensey resource, we
// retry immediately.
// TODO: Refactor throwException so that we don't have to do this type
// check. The caller already knows what the cause was.
//
// 检查这是否是一个 Suspense 类型的资源。我们不会为它们附加重试
// 监听器,因为在渲染时我们实际上不需要它们。仅在提交时需要。
// 相反,如果回退提交了,而唯一挂起的只是一个 Suspense 类型的资源,
// 我们会立即重试。
// 待办:重构 throwException,这样我们就不必进行这种类型检查。
// 调用方已经知道原因了。
const isSuspenseyResource =
wakeable === noopSuspenseyCommitThenable;
if (isSuspenseyResource) {
suspenseBoundary.flags |= ScheduleRetry;
} else {
const retryQueue: RetryQueue | null =
suspenseBoundary.updateQueue as any;
if (retryQueue === null) {
suspenseBoundary.updateQueue = new Set([wakeable]);
} else {
retryQueue.add(wakeable);
}
// We only attach ping listeners in concurrent mode. Legacy
// Suspense always commits fallbacks synchronously, so there are
// no pings.
//
// 我们只在并发模式下附加 ping 监听器。Legacy
// Suspense 总是同步提交回退,因此不会有 ping。
if (disableLegacyMode || suspenseBoundary.mode & ConcurrentMode) {
attachPingListener(root, wakeable, rootRenderLanes);
}
}
return false;
}
case OffscreenComponent: {
if (disableLegacyMode || suspenseBoundary.mode & ConcurrentMode) {
suspenseBoundary.flags |= ShouldCapture;
const isSuspenseyResource =
wakeable === noopSuspenseyCommitThenable;
if (isSuspenseyResource) {
suspenseBoundary.flags |= ScheduleRetry;
} else {
const offscreenQueue: OffscreenQueue | null =
suspenseBoundary.updateQueue as any;
if (offscreenQueue === null) {
const newOffscreenQueue: OffscreenQueue = {
transitions: null,
markerInstances: null,
retryQueue: new Set([wakeable]),
};
suspenseBoundary.updateQueue = newOffscreenQueue;
} else {
const retryQueue = offscreenQueue.retryQueue;
if (retryQueue === null) {
offscreenQueue.retryQueue = new Set([wakeable]);
} else {
retryQueue.add(wakeable);
}
}
attachPingListener(root, wakeable, rootRenderLanes);
}
return false;
}
}
}
throw new Error(
`Unexpected Suspense handler tag (${suspenseBoundary.tag}). This ` +
'is a bug in React.',
);
} else {
// No boundary was found. Unless this is a sync update, this is OK.
// We can suspend and wait for more data to arrive.
// 没有找到边界。除非这是一次同步更新,否则这是正常的。
// 我们可以暂停并等待更多数据到达。
if (disableLegacyMode || root.tag === ConcurrentRoot) {
// In a concurrent root, suspending without a Suspense boundary is
// allowed. It will suspend indefinitely without committing.
//
// 在并发根中,在没有 Suspense 边界的情况下挂起是允许的。
// 它将无限期挂起而不提交。
//
// TODO: Should we have different behavior for discrete updates? What
// about flushSync? Maybe it should put the tree into an inert state,
// and potentially log a warning. Revisit this for a future release.
//
// TODO:我们是否应该对离散更新有不同的行为?flushSync呢?也许它应该将树置于惰性状态,
// 并且可能记录一个警告。未来版本再重新考虑这个问题。
attachPingListener(root, wakeable, rootRenderLanes);
renderDidSuspendDelayIfPossible();
return false;
} else {
// In a legacy root, suspending without a boundary is always an error.
// 在旧版根节点中,无边界挂起始终是错误的。
const uncaughtSuspenseError = new Error(
'A component suspended while responding to synchronous input. This ' +
'will cause the UI to be replaced with a loading indicator. To ' +
'fix, updates that suspend should be wrapped ' +
'with startTransition.',
);
value = uncaughtSuspenseError;
}
}
}
}
// This is a regular error, not a Suspense wakeable.
// 这是一个普通错误,不是 Suspense 可唤醒对象。
if (
getIsHydrating() &&
(disableLegacyMode || sourceFiber.mode & ConcurrentMode)
) {
markDidThrowWhileHydratingDEV();
const hydrationBoundary = getSuspenseHandler();
// If the error was thrown during hydration, we may be able to recover by
// discarding the dehydrated content and switching to a client render.
// Instead of surfacing the error, find the nearest Suspense boundary
// and render it again without hydration.
//
// 如果错误是在水合过程中抛出的,我们可能可以通过丢弃已脱水的内容并切换到客户端渲染来恢复。
// 不直接显示错误,而是找到最近的 Suspense 边界
// 并在没有水合的情况下再次渲染它。
if (hydrationBoundary !== null) {
if (__DEV__) {
if (hydrationBoundary.tag === SuspenseListComponent) {
console.error(
'SuspenseList should never catch while hydrating. This is a bug in React.',
);
}
}
if ((hydrationBoundary.flags & ShouldCapture) === NoFlags) {
// Set a flag to indicate that we should try rendering the normal
// children again, not the fallback.
//
// 设置一个标志,表示我们应该尝试再次渲染正常的子元素,而不是回退内容。
hydrationBoundary.flags |= ForceClientRender;
}
markSuspenseBoundaryShouldCapture(
hydrationBoundary,
returnFiber,
sourceFiber,
root,
rootRenderLanes,
);
// Even though the user may not be affected by this error, we should
// still log it so it can be fixed.
//
// 即使用户可能不会受到此错误的影响,我们仍然应当记录它以便修复。
if (value !== HydrationMismatchException) {
const wrapperError = new Error(
'There was an error while hydrating but React was able to recover by ' +
'instead client rendering from the nearest Suspense boundary.',
{ cause: value },
);
queueHydrationError(
createCapturedValueAtFiber(wrapperError, sourceFiber),
);
}
return false;
} else {
if (value !== HydrationMismatchException) {
const wrapperError = new Error(
'There was an error while hydrating but React was able to recover by ' +
'instead client rendering the entire root.',
{ cause: value },
);
queueHydrationError(
createCapturedValueAtFiber(wrapperError, sourceFiber),
);
}
const workInProgress: Fiber = (root.current as any).alternate;
// Schedule an update at the root to log the error but this shouldn't
// actually happen because we should recover.
//
// 在根节点安排一次更新以记录错误,但这实际上不应该发生,因为我们应该能恢复。
workInProgress.flags |= ShouldCapture;
const lane = pickArbitraryLane(rootRenderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
const rootErrorInfo = createCapturedValueAtFiber(value, sourceFiber);
const update = createRootErrorUpdate(
workInProgress.stateNode,
// 由于恢复机制,这条日志实际上永远不会被记录。
rootErrorInfo, // This should never actually get logged due to the recovery.
lane,
);
enqueueCapturedUpdate(workInProgress, update);
renderDidError();
return false;
}
} else {
// Otherwise, fall through to the error path.
// 否则,继续执行错误路径。
}
const wrapperError = new Error(
'There was an error during concurrent rendering but React was able to recover by ' +
'instead synchronously rendering the entire root.',
{ cause: value },
);
queueConcurrentError(createCapturedValueAtFiber(wrapperError, sourceFiber));
renderDidError();
// We didn't find a boundary that could handle this type of exception. Start
// over and traverse parent path again, this time treating the exception
// as an error.
//
// 我们没有找到可以处理此类异常的边界。请重新开始并再次遍历父路径,这一次将异常视为错误。
if (returnFiber === null) {
// There's no return fiber, which means the root errored. This should never
// happen. Return `true` to trigger a fatal error (panic).
//
// 没有返回 fiber,这意味着根节点出错了。这种情况不应该发生。
// 返回 `true` 会触发致命错误(panic)。
return true;
}
const errorInfo = createCapturedValueAtFiber(value, sourceFiber);
let workInProgress: Fiber = returnFiber;
do {
switch (workInProgress.tag) {
case HostRoot: {
workInProgress.flags |= ShouldCapture;
const lane = pickArbitraryLane(rootRenderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
const update = createRootErrorUpdate(
workInProgress.stateNode,
errorInfo,
lane,
);
enqueueCapturedUpdate(workInProgress, update);
return false;
}
case ClassComponent:
// Capture and retry
// 捕获并重试
const ctor = workInProgress.type;
const instance = workInProgress.stateNode;
if (
(workInProgress.flags & DidCapture) === NoFlags &&
(typeof ctor.getDerivedStateFromError === 'function' ||
(instance !== null &&
typeof instance.componentDidCatch === 'function' &&
!isAlreadyFailedLegacyErrorBoundary(instance)))
) {
workInProgress.flags |= ShouldCapture;
const lane = pickArbitraryLane(rootRenderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
// Schedule the error boundary to re-render using updated state
// 使用更新后的状态安排错误边界重新渲染
const update = createClassErrorUpdate(lane);
initializeClassErrorUpdate(update, root, workInProgress, errorInfo);
enqueueCapturedUpdate(workInProgress, update);
return false;
}
break;
case OffscreenComponent: {
const offscreenState: OffscreenState | null =
workInProgress.memoizedState as any;
if (offscreenState !== null) {
// An error was thrown inside a hidden Offscreen boundary. This should
// not be allowed to escape into the visible part of the UI. Mark the
// boundary with ShouldCapture to abort the ongoing prerendering
// attempt. This is the same flag would be set if something were to
// suspend. It will be cleared the next time the boundary
// is attempted.
//
// 在隐藏的离屏边界内抛出了一个错误。这不应该允许逃逸到 UI 的可见部分。将边界
// 标记为 ShouldCapture 以中止正在进行的预渲染尝试。如果有某些操作被挂起,也会
// 设置相同的标志。下次尝试访问该边界时,该标志将被清除。
workInProgress.flags |= ShouldCapture;
return false;
}
break;
}
default:
break;
}
workInProgress = workInProgress.return;
} while (workInProgress !== null);
return false;
}
六、工具
1. 重置挂起组件
备注
propagateParentContextChangesToDeferredTree()由 ReactFiberNewContext#propagateParentContextChangesToDeferredTree 实现
function resetSuspendedComponent(sourceFiber: Fiber, rootRenderLanes: Lanes) {
const currentSourceFiber = sourceFiber.alternate;
if (currentSourceFiber !== null) {
// Since we never visited the children of the suspended component, we
// need to propagate the context change now, to ensure that we visit
// them during the retry.
//
// 由于我们从未访问挂起组件的子组件,
// 现在需要传播上下文的变化,以确保在重试时访问它们。
//
// We don't have to do this for errors because we retry errors without
// committing in between. So this is specific to Suspense.
//
// 我们不必对错误这样做,因为我们在中间不提交时会重试错误。
// 所以这只针对 Suspense。
propagateParentContextChangesToDeferredTree(
currentSourceFiber,
sourceFiber,
rootRenderLanes,
);
}
}
2. 标记挂起边界应捕获
备注
createUpdate()由 ReactFiberClassUpdateQueue#createUpdate 实现enqueueUpdate()由 [ReactFiberClassUpdateQueue#enqueueUpdate] 实现mergeLanes()由 ReactFiberLane#mergeLanes 实现
function markSuspenseBoundaryShouldCapture(
suspenseBoundary: Fiber,
returnFiber: Fiber | null,
sourceFiber: Fiber,
root: FiberRoot,
rootRenderLanes: Lanes,
): Fiber | null {
// This marks a Suspense boundary so that when we're unwinding the stack,
// it captures the suspended "exception" and does a second (fallback) pass.
//
// 这标记了一个 Suspense 边界,因此当我们展开堆栈时,
// 它会捕获挂起的“异常”并进行第二次(回退)处理。
if (
!disableLegacyMode &&
(suspenseBoundary.mode & ConcurrentMode) === NoMode
) {
// Legacy Mode Suspense
// 传统模式挂起
//
// If the boundary is in legacy mode, we should *not*
// suspend the commit. Pretend as if the suspended component rendered
// null and keep rendering. When the Suspense boundary completes,
// we'll do a second pass to render the fallback.
//
// 如果边界处于旧版模式,我们不应该挂起提交。
// 假装挂起的组件渲染了 null 并继续渲染。当 Suspense 边界完成时,
// 我们将进行第二次渲染以显示备用内容。
if (suspenseBoundary === returnFiber) {
// Special case where we suspended while reconciling the children of
// a Suspense boundary's inner Offscreen wrapper fiber. This happens
// when a React.lazy component is a direct child of a
// Suspense boundary.
//
// 特殊情况:当我们在协调 Suspense 边界内部 Offscreen 包装器 fiber 的
// 子节点时发生挂起。这个情况发生在 React.lazy 组件是 Suspense 边界的直接子节点时。
//
// Suspense boundaries are implemented as multiple fibers, but they
// are a single conceptual unit. The legacy mode behavior where we
// pretend the suspended fiber committed as `null` won't work,
// because in this case the "suspended" fiber is the inner
// Offscreen wrapper.
//
// Suspense 边界是以多个 fiber 实现的,但它们是一个单一的概念单元。在旧版
// 模式下,我们假装被挂起的 fiber 提交为 `null` 的行为将不起作用,因为在这
// 种情况下,被“挂起”的 fiber 是内部的 Offscreen 包装器。
//
// Because the contents of the boundary haven't started rendering
// yet (i.e. nothing in the tree has partially rendered) we can
// switch to the regular, concurrent mode behavior: mark the
// boundary with ShouldCapture and enter the unwind phase.
//
// 因为边界的内容尚未开始渲染
// (即树中的内容还没有部分渲染),我们可以
// 切换到常规的并发模式行为:将边界标记为 ShouldCapture 并进入展开阶段。
suspenseBoundary.flags |= ShouldCapture;
} else {
suspenseBoundary.flags |= DidCapture;
sourceFiber.flags |= ForceUpdateForLegacySuspense;
// We're going to commit this fiber even though it didn't complete.
// But we shouldn't call any lifecycle methods or callbacks. Remove
// all lifecycle effect tags.
//
// 即使该 fiber 尚未完成,我们仍将提交它。
// 但是我们不应该调用任何生命周期方法或回调。移除
// 所有生命周期效果标签。
sourceFiber.flags &= ~(LifecycleEffectMask | Incomplete);
if (sourceFiber.tag === ClassComponent) {
const currentSourceFiber = sourceFiber.alternate;
if (currentSourceFiber === null) {
// This is a new mount. Change the tag so it's not mistaken for a
// completed class component. For example, we should not call
// componentWillUnmount if it is deleted.
//
// 这是一个新的挂载。更改标签,以免被误认为是已完成的类组件。
// 例如,如果它被删除,我们不应该调用 componentWillUnmount。
sourceFiber.tag = IncompleteClassComponent;
} else {
// When we try rendering again, we should not reuse the current fiber,
// since it's known to be in an inconsistent state. Use a force update to
// prevent a bail out.
//
// 当我们尝试再次渲染时,不应重用当前的 fiber,
// 因为它的状态已知不一致。使用强制更新可以
// 防止跳过渲染。
const update = createUpdate(SyncLane);
update.tag = ForceUpdate;
enqueueUpdate(sourceFiber, update, SyncLane);
}
} else if (sourceFiber.tag === FunctionComponent) {
const currentSourceFiber = sourceFiber.alternate;
if (currentSourceFiber === null) {
// This is a new mount. Change the tag so it's not mistaken for a
// completed function component.
//
// 这是一个新的挂载。修改标签,以免被误认为是已完成的函数组件。
sourceFiber.tag = IncompleteFunctionComponent;
}
}
// The source fiber did not complete. Mark it with Sync priority to
// indicate that it still has pending work.
//
// 源 fiber 未完成。将其标记为同步优先级,以表示它仍有待处理的工作。
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, SyncLane);
}
return suspenseBoundary;
}
// Confirmed that the boundary is in a concurrent mode tree. Continue
// with the normal suspend path.
//
// 已确认边界处于并发模式树中。继续
// 按正常挂起路径处理。
//
// After this we'll use a set of heuristics to determine whether this
// render pass will run to completion or restart or "suspend" the commit.
// The actual logic for this is spread out in different places.
//
// 在此之后,我们将使用一组启发式方法来确定此渲染过程是会完成执行还是重新启动,
// 或者“挂起”提交。实际的逻辑分布在不同的地方。
//
// This first principle is that if we're going to suspend when we complete
// a root, then we should also restart if we get an update or ping that
// might unsuspend it, and vice versa. The only reason to suspend is
// because you think you might want to restart before committing. However,
// it doesn't make sense to restart only while in the period we're suspended.
//
// 第一个原则是,如果我们在完成根节点时要暂挂,那么如果我们收到可能会取消暂挂的更新
// 或 ping,也应该重新启动,反之亦然。暂挂的唯一原因是因为你可能希望在提交前重新启动。
// 然而,仅仅在我们处于暂挂期间才重新启动是没有意义的。
//
// Restarting too aggressively is also not good because it starves out any
// intermediate loading state. So we use heuristics to determine when.
//
// 过于激进地重启也不好,因为这会抑制任何中间加载状态。因此,我们使用启发式方法来决定何时重启。
// Suspense Heuristics
// 挂起启发式
//
// If nothing threw a Promise or all the same fallbacks are already showing,
// then don't suspend/restart.
//
// 如果没有任何东西抛出 Promise,或者所有相同的备用内容已经显示,
// 那么就不要挂起/重启。
//
// If this is an initial render of a new tree of Suspense boundaries and
// those trigger a fallback, then don't suspend/restart. We want to ensure
// that we can show the initial loading state as quickly as possible.
//
// 如果这是 Suspense 边界新树的初始渲染
// 并且触发了回退,那么不要挂起/重新启动。
// 我们希望确保能够尽快显示初始加载状态。
//
// If we hit a "Delayed" case, such as when we'd switch from content back into
// a fallback, then we should always suspend/restart. Transitions apply
// to this case. If none is defined, JND is used instead.
//
// 如果我们遇到“延迟”情况,例如当我们从内容切换回备用时,
// 我们应该始终挂起/重新启动。过渡适用于这种情况。
// 如果没有定义,则使用 JND。
//
// If we're already showing a fallback and it gets "retried", allowing us to show
// another level, but there's still an inner boundary that would show a fallback,
// then we suspend/restart for 500ms since the last time we showed a fallback
// anywhere in the tree. This effectively throttles progressive loading into a
// consistent train of commits. This also gives us an opportunity to restart to
// get to the completed state slightly earlier.
//
// 如果我们已经显示了一个后备内容,并且它被“重试”,允许我们显示另一个层级,
// 但是仍然存在一个内部边界会显示后备内容,
// 那么自上次在树中的任何地方显示后备内容以来,我们会暂停/重启 500 毫秒。
// 这实际上会将渐进加载节流为一个一致的提交序列。
// 这也给了我们一个机会,通过重启稍早地达到完成状态。
//
// If there's ambiguity due to batching it's resolved in preference of:
// 1) "delayed", 2) "initial render", 3) "retry".
//
// 如果由于批处理而存在歧义,则按以下优先顺序解决:
// - 1) “延迟”
// - 2) “初始渲染”
// - 3) “重试”
//
// We want to ensure that a "busy" state doesn't get force committed. We want to
// ensure that new initial loading states can commit as soon as possible.
//
// 我们希望确保“忙碌”状态不会被强制提交。我们希望确保新的初始加载状态可以尽快提交。
suspenseBoundary.flags |= ShouldCapture;
// TODO: I think we can remove this, since we now use `DidCapture` in
// the begin phase to prevent an early bailout.
//
// 待办:我认为我们可以移除这个,因为我们现在在开始阶段使用 `DidCapture` 来防止提前退出。
suspenseBoundary.lanes = rootRenderLanes;
return suspenseBoundary;
}