跳到主要内容

RF 开始工作

一、作用

二、导出的常量

1. 选择性水合异常

// A special exception that's used to unwind the stack when an update flows
// into a dehydrated boundary.
//
// 一种特殊的异常,用于在更新流入脱水边界时展开堆栈。
export const SelectiveHydrationException: mixed = new Error(
"This is not a real error. It's an implementation detail of React's " +
"selective hydration feature. If this leaks into userspace, it's a bug in " +
'React. Please file an issue.',
);

三、导出的变量

1. 已警告关于重新分配属性

export let didWarnAboutReassigningProps: boolean;

四、重放功能组件

备注
export function replayFunctionComponent(
current: Fiber | null,
workInProgress: Fiber,
nextProps: any,
Component: any,
secondArg: any,
renderLanes: Lanes,
): Fiber | null {
// This function is used to replay a component that previously suspended,
// after its data resolves. It's a simplified version of
// updateFunctionComponent that reuses the hooks from the previous attempt.
//
// 这个函数用于在先前挂起的组件的数据解析后重新渲染它。
// 它是 updateFunctionComponent 的简化版本,
// 重用了上一次尝试时的 hooks。

prepareToReadContext(workInProgress, renderLanes);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
const nextChildren = replaySuspendedComponentWithHooks(
current,
workInProgress,
Component,
nextProps,
secondArg,
);
const hasId = checkDidRenderIdHook();
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}

if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}

// React DevTools reads this flag.
// React DevTools 会读取此标志。
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

五、标记工作进展已接收更新

export function markWorkInProgressReceivedUpdate() {
didReceiveUpdate = true;
}

六、检查工作进展是否收到更新

export function checkIfWorkInProgressReceivedUpdate(): boolean {
return didReceiveUpdate;
}

七、协调子元素

备注
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
//
// 如果这是一个尚未渲染的新组件,
// 我们不会通过应用最小副作用来更新它的子集。
// 相反,我们会在渲染前将它们全部添加到子组件中。
// 这意味着我们可以通过不跟踪副作用来优化这次协调过程。
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
//
// 如果当前子节点与正在进行的工作相同,这意味着我们尚未开始处理这些子节点。因此,我们使
// 用克隆算法来创建所有当前子节点的副本。

// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
//
// 如果我们已经有了任何进展的工作,那此时都无效,所以让我们丢掉它。
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}

八、开始干活

备注
源码,约 322 行
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (__DEV__) {
if (workInProgress._debugNeedsRemount && current !== null) {
// This will restart the begin phase with a new fiber.
// 这将用一个新的 fiber 重新启动 begin 阶段。
const copiedFiber = createFiberFromTypeAndProps(
workInProgress.type,
workInProgress.key,
workInProgress.pendingProps,
workInProgress._debugOwner || null,
workInProgress.mode,
workInProgress.lanes,
);
copiedFiber._debugStack = workInProgress._debugStack;
copiedFiber._debugTask = workInProgress._debugTask;
return remountFiber(current, workInProgress, copiedFiber);
}
}

if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;

if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
// 如果由于热重载实现发生变化,则强制重新渲染:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
//
// 如果 props 或 context 发生变化,标记 fiber 已经执行过工作。
// 如果 later(memo)确定 props 相等,这可能会被取消。
didReceiveUpdate = true;
} else {
// Neither props nor legacy context changes. Check if there's a pending
// update or context change.
//
// props 和旧版 context 都没有变化。检查是否有待处理的更新或 context 变化。
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
// If this is the second pass of an error or suspense boundary, there
// may not be work scheduled on `current`, so we check for this flag.
//
// 如果这是错误边界或挂起边界的第二次传递,
// 可能在 `current` 上没有安排任何工作,因此我们检查这个标志。
(workInProgress.flags & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
// 没有待处理的更新或上下文。现在退出。
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
//
// 这是一个仅存在于旧模式下的特殊情况。
// 请参阅 https://github.com/facebook/react/pull/19216。
didReceiveUpdate = true;
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
//
// 在此 fiber 上计划了更新,但没有新的 props
// 或者遗留的 context。将其设置为 false。如果更新队列或 context
// 使用者产生了改变的值,它会将其设置为 true。否则,
// 组件将假设子组件没有改变并退出。
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;

if (getIsHydrating() && isForkedChild(workInProgress)) {
// Check if this child belongs to a list of muliple children in
// its parent.
//
// 检查这个子元素是否属于其父元素中的多个子元素列表。
//
// In a true multi-threaded implementation, we would render children on
// parallel threads. This would represent the beginning of a new render
// thread for this subtree.
//
// 在真正的多线程实现中,我们会在并行线程上渲染子元素。
// 这将代表为此子树开启一个新的渲染线程的开始。
//
// We only use this for id generation during hydration, which is why the
// logic is located in this special branch.
//
// 我们仅在水合过程中使用它来生成 ID,这就是为什么逻辑位于这个特殊分支中的原因。
const slotIndex = workInProgress.index;
const numberOfForks = getForksAtLevel(workInProgress);
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}

// Before entering the begin phase, clear pending update priority.
// TODO: This assumes that we're about to evaluate the component and process
// the update queue. However, there's an exception: SimpleMemoComponent
// sometimes bails out later in the begin phase. This indicates that we should
// move this assignment out of the common path and into each branch.
//
// 在进入 begin 阶段之前,清除待处理的更新优先级。
// TODO: 这假设我们即将评估组件并处理更新队列。
// 但是,有一个例外情况:SimpleMemoComponent
// 有时会在 begin 阶段稍后退出。这表明我们应该
// 将此赋值操作移出通用路径,并放入每个分支中。
workInProgress.lanes = NoLanes;

switch (workInProgress.tag) {
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
case FunctionComponent: {
const Component = workInProgress.type;
return updateFunctionComponent(
current,
workInProgress,
Component,
workInProgress.pendingProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = resolveClassComponentProps(
Component,
unresolvedProps,
);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostHoistable:
if (supportsResources) {
return updateHostHoistable(current, workInProgress, renderLanes);
}
// Fall through
// 贯穿
case HostSingleton:
if (supportsSingletons) {
return updateHostSingleton(current, workInProgress, renderLanes);
}
// Fall through
// 贯穿
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
case ForwardRef: {
return updateForwardRef(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
case Mode:
return updateMode(current, workInProgress, renderLanes);
case Profiler:
return updateProfiler(current, workInProgress, renderLanes);
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
case MemoComponent: {
return updateMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
case IncompleteClassComponent: {
if (disableLegacyMode) {
break;
}
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = resolveClassComponentProps(
Component,
unresolvedProps,
);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case IncompleteFunctionComponent: {
if (disableLegacyMode) {
break;
}
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = resolveClassComponentProps(
Component,
unresolvedProps,
);
return mountIncompleteFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case SuspenseListComponent: {
return updateSuspenseListComponent(current, workInProgress, renderLanes);
}
case ScopeComponent: {
if (enableScopeAPI) {
return updateScopeComponent(current, workInProgress, renderLanes);
}
break;
}
case ActivityComponent: {
return updateActivityComponent(current, workInProgress, renderLanes);
}
case OffscreenComponent: {
return updateOffscreenComponent(
current,
workInProgress,
renderLanes,
workInProgress.pendingProps,
);
}
case LegacyHiddenComponent: {
if (enableLegacyHidden) {
return updateLegacyHiddenComponent(
current,
workInProgress,
renderLanes,
);
}
break;
}
case CacheComponent: {
return updateCacheComponent(current, workInProgress, renderLanes);
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
return updateTracingMarkerComponent(
current,
workInProgress,
renderLanes,
);
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
return updateViewTransition(current, workInProgress, renderLanes);
}
break;
}
case Throw: {
// This represents a Component that threw in the reconciliation phase.
// So we'll rethrow here. This might be a Thenable.
//
// 这表示一个在协调阶段抛出异常的组件。
// 所以我们将在这里重新抛出。这可能是一个可 then 的对象。
throw workInProgress.pendingProps;
}
}

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. 已暂停

const SUSPENDED_MARKER: SuspenseState = {
dehydrated: null,
treeContext: null,
retryLane: NoLane,
hydrationErrors: null,
};

十、变量

1. 状态

备注

在源码中 320 - 323 、 325 - 338 行

// 收到更新
let didReceiveUpdate: boolean = false;

// 已警告不良类
let didWarnAboutBadClass;
// 已警告函数组件的上下文类型
let didWarnAboutContextTypeOnFunctionComponent;
// 已警告关于上下文类型
let didWarnAboutContextTypes;
// 已警告有关在函数组件上使用 getDerivedState
let didWarnAboutGetDerivedStateOnFunctionComponent;
// -

// 已警告关于显示顺序
let didWarnAboutRevealOrder;
// 已警告尾部选项
let didWarnAboutTailOptions;
// 已提醒关于视图过渡的类名
let didWarnAboutClassNameOnViewTransition;

if (__DEV__) {
didWarnAboutBadClass = {} as { [string]: boolean };
didWarnAboutContextTypeOnFunctionComponent = {} as { [string]: boolean };
didWarnAboutContextTypes = {} as { [string]: boolean };
didWarnAboutGetDerivedStateOnFunctionComponent = {} as { [string]: boolean };
didWarnAboutReassigningProps = false;
didWarnAboutRevealOrder = {} as { [string]: boolean };
didWarnAboutTailOptions = {} as { [string]: boolean };
didWarnAboutClassNameOnViewTransition = {} as { [string]: boolean };
}

2. 已警告不要在 ContextProvider 上使用 noValue 属性

备注

源码中的 3639 行

let hasWarnedAboutUsingNoValuePropOnContextProvider = false;

十一、工具

1. 强制卸载当前并协调

备注
function forceUnmountCurrentAndReconcile(
current: Fiber,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
// This function is fork of reconcileChildren. It's used in cases where we
// want to reconcile without matching against the existing set. This has the
// effect of all current children being unmounted; even if the type and key
// are the same, the old child is unmounted and a new child is created.
//
// To do this, we're going to go through the reconcile algorithm twice. In
// the first pass, we schedule a deletion for all the current children by
// passing null.
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
null,
renderLanes,
);
// In the second pass, we mount the new children. The trick here is that we
// pass null in place of where we usually pass the current child set. This has
// the effect of remounting all children regardless of whether their
// identities match.
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
}

2. 更新前向引用

备注
function updateForwardRef(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens after the first render suspends.
// We'll need to figure out if this is fine or can cause issues.
//
// 待办:即使组件尚未挂载,current 在这里也可能非空。这种情况发生在首次渲染挂起之后。
// 我们需要弄清楚这样是否没问题,或者是否会导致问题。
const render = Component.render;
const ref = workInProgress.ref;

let propsWithoutRef;
if ('ref' in nextProps) {
// `ref` is just a prop now, but `forwardRef` expects it to not appear in
// the props object. This used to happen in the JSX runtime, but now we do
// it here.
//
// `ref` 现在只是一个 prop,但 `forwardRef` 期望它不要出现在
// props 对象中。这在以前的 JSX 运行时会发生,但现在我们在这里处理。
propsWithoutRef = {} as { [string]: any };
for (const key in nextProps) {
// Since `ref` should only appear in props via the JSX transform, we can
// assume that this is a plain object. So we don't need a
// hasOwnProperty check.
//
// 由于 `ref` 应该只通过 JSX 转换出现在 props 中,我们可以
// 假设这是一个普通对象。所以我们不需要
// 使用 hasOwnProperty 检查。
if (key !== 'ref') {
propsWithoutRef[key] = nextProps[key];
}
}
} else {
propsWithoutRef = nextProps;
}

// The rest is a fork of updateFunctionComponent
// 其余部分是 updateFunctionComponent 的一个分支
prepareToReadContext(workInProgress, renderLanes);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}

const nextChildren = renderWithHooks(
current,
workInProgress,
render,
propsWithoutRef,
ref,
renderLanes,
);
const hasId = checkDidRenderIdHook();

if (enableSchedulingProfiler) {
markComponentRenderStopped();
}

if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}

// React DevTools reads this flag.
// React DevTools 会读取此标志。
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

3. 更新备忘组件

备注
function updateMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
if (current === null) {
const type = Component.type;
if (isSimpleFunctionComponent(type) && Component.compare === null) {
let resolvedType = type;
if (__DEV__) {
resolvedType = resolveFunctionForHotReloading(type);
}
// If this is a plain function component without default props,
// and with only the default shallow comparison, we upgrade it
// to a SimpleMemoComponent to allow fast path updates.
//
// 如果这是一个没有默认属性的普通函数组件,
// 并且只有默认的浅层比较,我们将其升级为
// 一个 SimpleMemoComponent,以允许快速路径更新。
workInProgress.tag = SimpleMemoComponent;
workInProgress.type = resolvedType;
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, type);
}
return updateSimpleMemoComponent(
current,
workInProgress,
resolvedType,
nextProps,
renderLanes,
);
}
const child = createFiberFromTypeAndProps(
Component.type,
null,
nextProps,
workInProgress,
workInProgress.mode,
renderLanes,
);
child.ref = workInProgress.ref;
child.return = workInProgress;
workInProgress.child = child;
return child;
}
// 这总是恰好一个子节点
const currentChild = current.child as any as Fiber; // This is always exactly one child
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (!hasScheduledUpdateOrContext) {
// This will be the props with resolved defaultProps,
// unlike current.memoizedProps which will be the unresolved ones.
//
// 这将是带有已解析默认属性的 props,
// 与 current.memoizedProps 不同,后者是未解析的 props。
const prevProps = currentChild.memoizedProps;
// Default to shallow comparison
// 默认为浅比较
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// React DevTools reads this flag.
// React DevTools 会读取此标志。
workInProgress.flags |= PerformedWork;
const newChild = createWorkInProgress(currentChild, nextProps);
newChild.ref = workInProgress.ref;
newChild.return = workInProgress;
workInProgress.child = newChild;
return newChild;
}

4. 更新简单备忘组件

function updateSimpleMemoComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
): null | Fiber {
// TODO: current can be non-null here even if the component
// hasn't yet mounted. This happens when the inner render suspends.
// We'll need to figure out if this is fine or can cause issues.
//
// 待办:即使组件尚未挂载,current 在这里也可能非空。这种情况发生在内部渲染挂起时。
// 我们需要弄清楚这样是否没问题,或者可能会导致问题。
if (current !== null) {
const prevProps = current.memoizedProps;
if (
shallowEqual(prevProps, nextProps) &&
current.ref === workInProgress.ref &&
// Prevent bailout if the implementation changed due to hot reload.
// 如果实现因热重载而改变,则防止中止。
(__DEV__ ? workInProgress.type === current.type : true)
) {
didReceiveUpdate = false;

// The props are shallowly equal. Reuse the previous props object, like we
// would during a normal fiber bailout.
//
// 这些属性是浅层相等的。重用之前的属性对象,就像我们在正常的 fiber 回退中会做的那样。
//
// We don't have strong guarantees that the props object is referentially
// equal during updates where we can't bail out anyway — like if the props
// are shallowly equal, but there's a local state or context update in the
// same batch.
//
// 我们不能强烈保证在无法直接跳过更新的情况下 props 对象在更新期间是引用相等的——比如
// 当 props 浅比较相等,但同一批次中有本地状态或上下文更新时。
//
// However, as a principle, we should aim to make the behavior consistent
// across different ways of memoizing a component. For example, React.memo
// has a different internal Fiber layout if you pass a normal function
// component (SimpleMemoComponent) versus if you pass a different type
// like forwardRef (MemoComponent). But this is an implementation detail.
// Wrapping a component in forwardRef (or React.lazy, etc) shouldn't
// affect whether the props object is reused during a bailout.
//
// 然而,作为一个原则,我们应该尽量让行为在不同的组件记忆方式之间保持一致。例如,如果
// 你传入一个普通函数组件(SimpleMemoComponent),React.memo 在内部 Fiber 布局
// 上会有所不同,而如果你传入另一种类型,比如 forwardRef(MemoComponent),情况也
// 会不同。但这是实现细节。将组件包裹在 forwardRef(或 React.lazy 等)中,不应影响
// 在跳过渲染时 props 对象是否被重用。
workInProgress.pendingProps = nextProps = prevProps;

if (!checkScheduledUpdateOrContext(current, renderLanes)) {
// The pending lanes were cleared at the beginning of beginWork. We're
// about to bail out, but there might be other lanes that weren't
// included in the current render. Usually, the priority level of the
// remaining updates is accumulated during the evaluation of the
// component (i.e. when processing the update queue). But since since
// we're bailing out early *without* evaluating the component, we need
// to account for it here, too. Reset to the value of the current fiber.
// NOTE: This only applies to SimpleMemoComponent, not MemoComponent,
// because a MemoComponent fiber does not have hooks or an update queue;
// rather, it wraps around an inner component, which may or may not
// contains hooks.
//
// 待处理的 lane 已在 beginWork 开始时被清理。
// 我们即将提前退出,但可能还有其他 lane 未包含在当前渲染中。
// 通常,剩余更新的优先级是在组件评估期间累积的(即处理更新队列时)。
// 但由于我们在未评估组件的情况下提前退出,我们也需要在这里处理它。
// 重置为当前 fiber 的值。
// 注意:这只适用于 SimpleMemoComponent,而不适用于 MemoComponent,
// 因为 MemoComponent fiber 没有 hooks 或更新队列;
// 它是包裹在一个内部组件周围,该内部组件可能包含也可能不包含 hooks。
//
// TODO: Move the reset at in beginWork out of the common path so that
// this is no longer necessary.
//
// 待办事项:将 beginWork 中的重置移出公共路径,这样就不再需要了。
workInProgress.lanes = current.lanes;
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
//
// 这是一个仅存在于旧模式下的特殊情况。
// 请参阅 https://github.com/facebook/react/pull/19216。
didReceiveUpdate = true;
}
}
}
return updateFunctionComponent(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}

5. 更新屏幕外组件

备注
function updateOffscreenComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
nextProps: OffscreenProps,
) {
const nextChildren = nextProps.children;

const prevState: OffscreenState | null =
current !== null ? current.memoizedState : null;

if (current === null && workInProgress.stateNode === null) {
// We previously reset the work-in-progress.
// We need to create a new Offscreen instance.
//
// 我们之前重置了正在进行的工作。
// 我们需要创建一个新的 Offscreen 实例。
const primaryChildInstance: OffscreenInstance = {
_visibility: OffscreenVisible,
_pendingMarkers: null,
_retryCache: null,
_transitions: null,
};
workInProgress.stateNode = primaryChildInstance;
}

if (
nextProps.mode === 'hidden' ||
(enableLegacyHidden && nextProps.mode === 'unstable-defer-without-hiding')
) {
// Rendering a hidden tree.
// 渲染一个隐藏的树。
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
if (didSuspend) {
// Something suspended inside a hidden tree
// 某物挂起在一棵隐藏的树里

// Include the base lanes from the last render
// 包含上一次渲染的基础图层
const nextBaseLanes =
prevState !== null
? mergeLanes(prevState.baseLanes, renderLanes)
: renderLanes;

let remainingChildLanes;
if (current !== null) {
// Reset to the current children
// 重置为当前子项
let currentChild = (workInProgress.child = current.child);

// The current render suspended, but there may be other lanes with
// pending work. We can't read `childLanes` from the current Offscreen
// fiber because we reset it when it was deferred; however, we can read
// the pending lanes from the child fibers.
//
// 当前渲染已暂停,但可能有其他车道有待处理的工作。我们无法从当前的离屏 fiber 中读
// 取 `childLanes`,因为在它被延迟时我们已经重置了它;不过,我们可以从子 fiber 中
// 读取待处理的车道。
let currentChildLanes: Lanes = NoLanes;
while (currentChild !== null) {
currentChildLanes = mergeLanes(
mergeLanes(currentChildLanes, currentChild.lanes),
currentChild.childLanes,
);
currentChild = currentChild.sibling;
}
const lanesWeJustAttempted = nextBaseLanes;
remainingChildLanes = removeLanes(
currentChildLanes,
lanesWeJustAttempted,
);
} else {
remainingChildLanes = NoLanes;
workInProgress.child = null;
}

return deferHiddenOffscreenComponent(
current,
workInProgress,
nextBaseLanes,
renderLanes,
remainingChildLanes,
);
}

if (
!disableLegacyMode &&
(workInProgress.mode & ConcurrentMode) === NoMode
) {
// In legacy sync mode, don't defer the subtree. Render it now.
// TODO: Consider how Offscreen should work with transitions in the future
//
// 在传统同步模式下,不要延迟子树。现在就渲染它。
// 待办:考虑未来 Offscreen 应该如何与过渡一起工作
const nextState: OffscreenState = {
baseLanes: NoLanes,
cachePool: null,
};
workInProgress.memoizedState = nextState;
// push the cache pool even though we're going to bail out
// because otherwise there'd be a context mismatch
// 推送缓存池,即使我们打算退出
// 否则会出现上下文不匹配
if (current !== null) {
pushTransition(workInProgress, null, null);
}
reuseHiddenContextOnStack(workInProgress);
pushOffscreenSuspenseHandler(workInProgress);
} else if (!includesSomeLane(renderLanes, OffscreenLane as Lane)) {
// We're hidden, and we're not rendering at Offscreen. We will bail out
// and resume this tree later.
// 我们被隐藏了,并且没有在离屏渲染。我们将退出,并稍后恢复这棵树。

// Schedule this fiber to re-render at Offscreen priority
// 将此 fiber 安排为以 Offscreen 优先级重新渲染
const remainingChildLanes = (workInProgress.lanes =
laneToLanes(OffscreenLane));

// Include the base lanes from the last render
// 包含上一次渲染的基础图层
const nextBaseLanes =
prevState !== null
? mergeLanes(prevState.baseLanes, renderLanes)
: renderLanes;

return deferHiddenOffscreenComponent(
current,
workInProgress,
nextBaseLanes,
renderLanes,
remainingChildLanes,
);
} else {
// This is the second render. The surrounding visible content has already
// committed. Now we resume rendering the hidden tree.
// 这是第二次渲染。周围可见内容已经提交。
// 现在我们恢复渲染隐藏的树。

// Rendering at offscreen, so we can clear the base lanes.
// 在离屏渲染,所以我们可以清除基础通道。
const nextState: OffscreenState = {
baseLanes: NoLanes,
cachePool: null,
};
workInProgress.memoizedState = nextState;
if (current !== null) {
// If the render that spawned this one accessed the cache pool, resume
// using the same cache. Unless the parent changed, since that means
// there was a refresh.
// 如果生成此渲染的上一个渲染访问了缓存池,则继续使用同一缓存。
// 除非父渲染发生了更改,因为那意味着缓存已刷新。
const prevCachePool = prevState !== null ? prevState.cachePool : null;
// TODO: Consider if and how Offscreen pre-rendering should
// be attributed to the transition that spawned it
// 待办事项:考虑是否以及如何将离屏预渲染归因于触发它的过渡
pushTransition(workInProgress, prevCachePool, null);
}

// Push the lanes that were skipped when we bailed out.
// 推送我们退出时跳过的通道。
if (prevState !== null) {
pushHiddenContext(workInProgress, prevState);
} else {
reuseHiddenContextOnStack(workInProgress);
}
pushOffscreenSuspenseHandler(workInProgress);
}
} else {
// Rendering a visible tree.
// 渲染一个可见的树。
if (prevState !== null) {
// We're going from hidden -> visible.
// 我们正在从隐藏状态变为可见。
let prevCachePool = null;
// If the render that spawned this one accessed the cache pool, resume
// using the same cache. Unless the parent changed, since that means
// there was a refresh.
//
// 如果生成此渲染的上一个渲染访问了缓存池,则继续使用相同的缓存。除非父渲染发生了
// 变化,因为那意味着进行了刷新。
prevCachePool = prevState.cachePool;

let transitions = null;
if (enableTransitionTracing) {
// We have now gone from hidden to visible, so any transitions should
// be added to the stack to get added to any Offscreen/suspense children
//
// 我们现在已经从隐藏变为可见,所以任何过渡效果都应该
// 被加入到堆栈中,以便添加到任何离屏/等待子组件
const instance: OffscreenInstance | null = workInProgress.stateNode;
if (instance !== null && instance._transitions != null) {
transitions = Array.from(instance._transitions);
}
}

pushTransition(workInProgress, prevCachePool, transitions);

// Push the lanes that were skipped when we bailed out.
// 推送我们退出时跳过的通道。
pushHiddenContext(workInProgress, prevState);
reuseSuspenseHandlerOnStack(workInProgress);

// Since we're not hidden anymore, reset the state
// 由于我们不再隐藏,重置状态
workInProgress.memoizedState = null;
} else {
// We weren't previously hidden, and we still aren't, so there's nothing
// special to do. Need to push to the stack regardless, though, to avoid
// a push/pop misalignment.
//
// 我们之前没有被隐藏,现在也没有,所以没有什么特别要做的。
// 无论如何都需要将其压入栈中,以避免压栈/弹栈不匹配。

// If the render that spawned this one accessed the cache pool, resume
// using the same cache. Unless the parent changed, since that means
// there was a refresh.
//
// 如果生成此渲染的上一个渲染访问了缓存池,则继续使用同一缓存。
// 除非父渲染发生了更改,因为那意味着缓存已刷新。
if (current !== null) {
pushTransition(workInProgress, null, null);
}

// We're about to bail out, but we need to push this to the stack anyway
// to avoid a push/pop misalignment.
//
// 我们即将退出,但我们仍然需要将其压入堆栈
// 以避免 push/pop 错位。
reuseHiddenContextOnStack(workInProgress);
reuseSuspenseHandlerOnStack(workInProgress);
}
}

reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

6. 离屏组件救援

function bailoutOffscreenComponent(
current: Fiber | null,
workInProgress: Fiber,
): Fiber | null {
if (
(current === null || current.tag !== OffscreenComponent) &&
workInProgress.stateNode === null
) {
const primaryChildInstance: OffscreenInstance = {
_visibility: OffscreenVisible,
_pendingMarkers: null,
_retryCache: null,
_transitions: null,
};
workInProgress.stateNode = primaryChildInstance;
}

return workInProgress.sibling;
}

7. 延迟隐藏的屏幕外组件

备注
function deferHiddenOffscreenComponent(
current: Fiber | null,
workInProgress: Fiber,
nextBaseLanes: Lanes,
renderLanes: Lanes,
remainingChildLanes: Lanes,
) {
const nextState: OffscreenState = {
baseLanes: nextBaseLanes,
// Save the cache pool so we can resume later.
// 保存缓存池,以便我们稍后可以继续。
cachePool: getOffscreenDeferredCache(),
};
workInProgress.memoizedState = nextState;
// push the cache pool even though we're going to bail out
// because otherwise there'd be a context mismatch
//
// 推送缓存池,即使我们打算退出
// 否则会出现上下文不匹配
if (current !== null) {
pushTransition(workInProgress, null, null);
}

// We're about to bail out, but we need to push this to the stack anyway
// to avoid a push/pop misalignment.
//
// 我们即将退出,但我们仍然需要将其压入堆栈
// 以避免 push/pop 错位。
reuseHiddenContextOnStack(workInProgress);

pushOffscreenSuspenseHandler(workInProgress);

if (current !== null) {
// Since this tree will resume rendering in a separate render, we need
// to propagate parent contexts now so we don't lose track of which
// ones changed.
//
// 由于这颗树将在一个单独的渲染中恢复渲染,我们现在需要传递父上下文,以免失去对
// 那些发生变化的上下文的跟踪。
propagateParentContextChangesToDeferredTree(
current,
workInProgress,
renderLanes,
);
}

// We override the remaining child lanes to be the subset that we computed
// on the outside. We need to do this after propagating the context
// because propagateParentContextChangesToDeferredTree may schedule
// work which bubbles all the way up to the root and updates our child lanes.
// We want to dismiss that since we're not going to work on it yet.
//
// 我们将剩余的子优先级覆盖为我们在外部计算的子集。
// 我们需要在传播上下文之后执行此操作
// 因为 propagateParentContextChangesToDeferredTree 可能会调度
// 工作,这会一直冒泡到根节点并更新我们的子优先级。
// 我们希望忽略这一点,因为我们还不会处理它。
workInProgress.childLanes = remainingChildLanes;

return null;
}

8. 更新遗留隐藏组件

function updateLegacyHiddenComponent(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps: LegacyHiddenProps = workInProgress.pendingProps;
// Note: These happen to have identical begin phases, for now. We shouldn't hold
// ourselves to this constraint, though. If the behavior diverges, we should
// fork the function.
// This just works today because it has the same Props.
//
// 注意:这些恰好目前有相同的开始阶段。不过,我们不应该拘泥于这个限制。如果行为出现差异,我们
// 应该分叉这个函数。
// 之所以今天能够工作,仅仅是因为它有相同的属性。
return updateOffscreenComponent(
current,
workInProgress,
renderLanes,
nextProps,
);
}

9. 挂载活动子组件

function mountActivityChildren(
workInProgress: Fiber,
nextProps: ActivityProps,
renderLanes: Lanes,
) {
if (__DEV__) {
const hiddenProp = (nextProps as any).hidden;
if (hiddenProp !== undefined) {
console.error(
'<Activity> doesn\'t accept a hidden prop. Use mode="hidden" instead.\n' +
'- <Activity %s>\n' +
'+ <Activity %s>',
hiddenProp === true
? 'hidden'
: hiddenProp === false
? 'hidden={false}'
: 'hidden={...}',
hiddenProp ? 'mode="hidden"' : 'mode="visible"',
);
}
}
const nextChildren = nextProps.children;
const nextMode = nextProps.mode;
const mode = workInProgress.mode;
const offscreenChildProps: OffscreenProps = {
mode: nextMode,
children: nextChildren,
};
const primaryChildFragment = mountWorkInProgressOffscreenFiber(
offscreenChildProps,
mode,
renderLanes,
);
primaryChildFragment.ref = workInProgress.ref;
workInProgress.child = primaryChildFragment;
primaryChildFragment.return = workInProgress;
return primaryChildFragment;
}

10. 在不重新填充的情况下重试活动组件

备注
function retryActivityComponentWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// Falling back to client rendering. Because this has performance
// implications, it's considered a recoverable error, even though the user
// likely won't observe anything wrong with the UI.
//
// 回退到客户端渲染。由于这会影响性能,虽然用户可能不会察觉到界面有任何异常,但这
// 仍被视为可恢复错误。

// This will add the old fiber to the deletion list
// 这将把旧的 fiber 添加到删除列表中
reconcileChildFibers(workInProgress, current.child, null, renderLanes);

// We're now not suspended nor dehydrated.
// 我们现在既没有挂起也没有脱水。
const nextProps: ActivityProps = workInProgress.pendingProps;
const primaryChildFragment = mountActivityChildren(
workInProgress,
nextProps,
renderLanes,
);
// Needs a placement effect because the parent (the Activity boundary) already
// mounted but this is a new fiber.
//
// 需要一个放置效果,因为父组件(Activity 边界)已经挂载,但这是一个新的 fiber。
primaryChildFragment.flags |= Placement;

// If we're not going to hydrate we can't leave it dehydrated if something
// suspends. In that case we want that to bubble to the nearest parent boundary
// so we need to pop our own handler that we just pushed.
//
// 如果我们不打算进行 hydration,就不能在某个东西挂起时让它保持脱水状态。
// 在这种情况下,我们希望它冒泡到最近的父边界,
// 所以我们需要弹出我们刚刚添加的处理器。
popSuspenseHandler(workInProgress);

workInProgress.memoizedState = null;

return primaryChildFragment;
}

11. 挂载脱水活动组件

备注
function mountDehydratedActivityComponent(
workInProgress: Fiber,
activityInstance: ActivityInstance,
renderLanes: Lanes,
): null | Fiber {
// During the first pass, we'll bail out and not drill into the children.
// Instead, we'll leave the content in place and try to hydrate it later.
// We'll continue hydrating the rest at offscreen priority since we'll already
// be showing the right content coming from the server, it is no rush.
//
// 在第一次遍历时,我们会直接退出,而不会深入处理子节点。
// 相反,我们会保留内容的位置,并尝试稍后进行水合。
// 我们会以离屏优先级继续进行其余部分的水合,因为我们已经
// 正确显示了来自服务器的内容,所以不着急。
workInProgress.lanes = laneToLanes(OffscreenLane);
return null;
}

12. 更新脱水活动组件

备注
function updateDehydratedActivityComponent(
current: Fiber,
workInProgress: Fiber,
didSuspend: boolean,
nextProps: ActivityProps,
activityInstance: ActivityInstance,
activityState: ActivityState,
renderLanes: Lanes,
): null | Fiber {
// We'll handle suspending since if something suspends we can just leave
// it dehydrated. We push early and then pop if we enter non-dehydrated attempts.
//
// 我们会处理挂起的情况,因为如果某些操作挂起,我们可以让它保持脱水状态。我们会提前推入,然后
// 如果进入非脱水尝试时再弹出。
pushDehydratedActivitySuspenseHandler(workInProgress);
if (!didSuspend) {
// This is the first render pass. Attempt to hydrate.
// 这是第一次渲染。尝试进行水合。

// We should never be hydrating at this point because it is the first pass,
// but after we've already committed once.
// 在这一点上我们绝不应该水合,因为这是第一次检查,
// 但在我们已经提交过一次之后可以水合。
warnIfHydrating();

if (includesSomeLane(renderLanes, OffscreenLane as Lane)) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
//
// 如果我们在离屏渲染并且正在进入活动,那么渲染的唯一原因可能是因为这个边界完成了工作。如果还
// 没有其他原因存在,就把它作为一个原因提供。
markRenderDerivedCause(workInProgress);
}

if (
// TODO: Factoring is a little weird, since we check this right below, too.
// 待办事项:因式分解有点奇怪,因为我们也在下面检查了这个。
!didReceiveUpdate
) {
// We need to check if any children have context before we decide to bail
// out, so propagate the changes now.
//
// 我们需要在决定放弃之前检查是否有子项具有上下文,
// 所以现在传播这些更改。
lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
}

// We use lanes to indicate that a child might depend on context, so if
// any context has changed, we need to treat is as if the input might have changed.
//
// 我们使用 lanes 来表示孩子可能依赖于上下文,所以如果任何上下文发生了变化,我们
// 需要将其视为输入可能已经改变。
const hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
if (didReceiveUpdate || hasContextChanged) {
// This boundary has changed since the first render. This means that we are now unable to
// hydrate it. We might still be able to hydrate it using a higher priority lane.
//
// 自第一次渲染以来,此边界已更改。这意味着我们现在无法对其进行 hydration。
// 我们仍然可能通过更高优先级的通道对其进行 hydration。
const root = getWorkInProgressRoot();
if (root !== null) {
const attemptHydrationAtLane = getBumpedLaneForHydration(
root,
renderLanes,
);
if (
attemptHydrationAtLane !== NoLane &&
attemptHydrationAtLane !== activityState.retryLane
) {
// Intentionally mutating since this render will get interrupted. This
// is one of the very rare times where we mutate the current tree
// during the render phase.
//
// 故意进行变更,因为这个渲染会被中断。
// 这是极少数我们在渲染阶段变更当前树的情况之一。
activityState.retryLane = attemptHydrationAtLane;
enqueueConcurrentRenderForLane(current, attemptHydrationAtLane);
scheduleUpdateOnFiber(root, current, attemptHydrationAtLane);

// Throw a special object that signals to the work loop that it should
// interrupt the current render.
//
// 抛出一个特殊对象,向工作循环发出信号,表示它应该中断当前渲染。
//
// Because we're inside a React-only execution stack, we don't
// strictly need to throw here — we could instead modify some internal
// work loop state. But using an exception means we don't need to
// check for this case on every iteration of the work loop. So doing
// it this way moves the check out of the fast path.
//
// 因为我们处在一个仅用于 React 的执行栈中,所以我们并不严格需要在这里抛出异常——我
// 们可以选择修改一些内部的工作循环状态。但使用异常意味着我们不需要在每次工作循环迭
// 代时都检查这种情况。所以以这种方式处理可以将检查移出快速路径。
throw SelectiveHydrationException;
} else {
// We have already tried to ping at a higher priority than we're rendering with
// so if we got here, we must have failed to hydrate at those levels. We must
// now give up. Instead, we're going to delete the whole subtree and instead inject
// a new real Activity boundary to take its place. This might suspend for a while
// and if it does we might still have an opportunity to hydrate before this pass
// commits.
//
// 我们已经尝试以比我们渲染时更高的优先级进行 ping
// 所以如果我们走到这里,说明我们在那些级别上水合失败了。我们必须
// 现在放弃。相反,我们将删除整个子树,并注入一个新的真实 Activity 边界来替代它。这可能会暂停一段时间
// 如果发生暂停,我们可能仍有机会在此次提交之前进行水合。
}
}

// If we did not selectively hydrate, we'll continue rendering without
// hydrating. Mark this tree as suspended to prevent it from committing
// outside a transition.
//
// 如果我们没有选择性地进行 hydration,我们将继续渲染而不进行 hydration。将此树标记
// 为挂起,以防止它在过渡之外提交。
//
// This path should only happen if the hydration lane already suspended.
// 只有在 hydration lane 已经挂起时才会发生此路径。
renderDidSuspendDelayIfPossible();
return retryActivityComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
);
} else {
// This is the first attempt.
// 这是第一次尝试。

reenterHydrationStateFromDehydratedActivityInstance(
workInProgress,
activityInstance,
activityState.treeContext,
);

const primaryChildFragment = mountActivityChildren(
workInProgress,
nextProps,
renderLanes,
);
// Mark the children as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
//
// 将子节点标记为正在进行 hydration(数据恢复)。这是快速判断该树是否属于正在 hydration 的
// 树的方法。用于确定子节点是否已经完全挂载,以及安排事件重放。
// 从概念上讲,这类似于 Placement(插入),因为一个新的子树在这里被插入到 React 树中。只是恰好不需
// 要 DOM 变更,因为它已经存在。
primaryChildFragment.flags |= Hydrating;
return primaryChildFragment;
}
} else {
// This is the second render pass. We already attempted to hydrated, but
// something either suspended or errored.
//
// 这是第二次渲染。我们已经尝试过进行 hydration,但
// 某些地方要么挂起了,要么出错了。

if (workInProgress.flags & ForceClientRender) {
// Something errored during hydration. Try again without hydrating.
// The error should've already been logged in throwException.
//
// 在水合过程中出现了错误。请尝试在不进行水合的情况下再次操作。
// 错误应该已经在 throwException 中记录过了。
workInProgress.flags &= ~ForceClientRender;
return retryActivityComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
);
} else if (
(workInProgress.memoizedState as null | ActivityState) !== null
) {
// Something suspended and we should still be in dehydrated mode.
// Leave the existing child in place.
//
// 某些东西被挂起了,我们仍然应该处于脱水模式。
// 保持现有的子项不变。

workInProgress.child = current.child;
// The dehydrated completion pass expects this flag to be there
// but the normal offscreen pass doesn't.
//
// 脱水完成过程期望此标志存在
// 但普通的离屏过程不需要。
workInProgress.flags |= DidCapture;
workInProgress.flags |= DidCapture;
return null;
} else {
// 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.',
);
}
}
}

13. 更新活动组件

备注
function updateActivityComponent(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps: ActivityProps = workInProgress.pendingProps;

// Check if the first pass suspended.
// 检查第一次传递是否已挂起。
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
workInProgress.flags &= ~DidCapture;

if (current === null) {
// Initial mount
// 初始挂载

// Special path for hydration
// If we're currently hydrating, try to hydrate this boundary.
// Hidden Activity boundaries are not emitted on the server.
//
// 用于水合的特殊路径
// 如果我们当前正在进行水合,尝试水合这个边界。
// 隐藏的 Activity 边界不会在服务器端生成。
if (getIsHydrating()) {
if (nextProps.mode === 'hidden') {
// SSR doesn't render hidden Activity so it shouldn't hydrate,
// even at offscreen lane. Defer to a client rendered offscreen lane.
//
// SSR 不会渲染隐藏的 Activity,因此不应该进行 hydration,
// 即使在屏幕外车道也是如此。应延迟到客户端渲染的屏幕外车道。
const primaryChildFragment = mountActivityChildren(
workInProgress,
nextProps,
renderLanes,
);
workInProgress.lanes = laneToLanes(OffscreenLane);
return bailoutOffscreenComponent(null, primaryChildFragment);
} else {
// We must push the suspense handler context *before* attempting to
// hydrate, to avoid a mismatch in case it errors.
//
// 我们必须在尝试水合之前先推送 suspense 处理器上下文,以防它出错导致不匹配。
pushDehydratedActivitySuspenseHandler(workInProgress);
const dehydrated: ActivityInstance =
claimNextHydratableActivityInstance(workInProgress);
return mountDehydratedActivityComponent(
workInProgress,
dehydrated,
renderLanes,
);
}
}

return mountActivityChildren(workInProgress, nextProps, renderLanes);
} else {
// This is an update.
// 这是一次更新。

// Special path for hydration
// 用于水合作用的特殊路径
const prevState: null | ActivityState = current.memoizedState;

if (prevState !== null) {
const dehydrated = prevState.dehydrated;
return updateDehydratedActivityComponent(
current,
workInProgress,
didSuspend,
nextProps,
dehydrated,
prevState,
renderLanes,
);
}

const currentChild: Fiber = current.child as any;

const nextChildren = nextProps.children;
const nextMode = nextProps.mode;
const offscreenChildProps: OffscreenProps = {
mode: nextMode,
children: nextChildren,
};

if (
includesSomeLane(renderLanes, OffscreenLane as Lane) &&
includesSomeLane(renderLanes, current.lanes)
) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
//
// 如果我们在离屏渲染并且正在进入活动,那么渲染的唯一原因可能是因为这个边界完成了工作。如果
// 还没有其他原因存在,就把它作为一个原因提供。
markRenderDerivedCause(workInProgress);
}

const primaryChildFragment = updateWorkInProgressOffscreenFiber(
currentChild,
offscreenChildProps,
);

primaryChildFragment.ref = workInProgress.ref;
workInProgress.child = primaryChildFragment;
primaryChildFragment.return = workInProgress;
return primaryChildFragment;
}
}

14. 更新缓存组件

备注
function updateCacheComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
prepareToReadContext(workInProgress, renderLanes);
const parentCache = readContext(CacheContext);

if (current === null) {
// Initial mount. Request a fresh cache from the pool.
// 初始挂载。从池中请求一个新的缓存。
const freshCache = requestCacheFromPool(renderLanes);
const initialState: CacheComponentState = {
parent: parentCache,
cache: freshCache,
};
workInProgress.memoizedState = initialState;
initializeUpdateQueue(workInProgress);
pushCacheProvider(workInProgress, freshCache);
} else {
// Check for updates
// 检查更新
if (includesSomeLane(current.lanes, renderLanes)) {
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, null, null, renderLanes);
suspendIfUpdateReadFromEntangledAsyncAction();
}
const prevState: CacheComponentState = current.memoizedState;
const nextState: CacheComponentState = workInProgress.memoizedState;

// Compare the new parent cache to the previous to see detect there was
// a refresh.
// 将新的父缓存与之前的缓存进行比较,以检测是否有刷新。
if (prevState.parent !== parentCache) {
// Refresh in parent. Update the parent.
// 在父级中刷新。更新父级。
const derivedState: CacheComponentState = {
parent: parentCache,
cache: parentCache,
};

// Copied from getDerivedStateFromProps implementation. Once the update
// queue is empty, persist the derived state onto the base state.
// 复制自 getDerivedStateFromProps 的实现。一旦更新队列为空,将派生状态持久化到基础状态上。
workInProgress.memoizedState = derivedState;
if (workInProgress.lanes === NoLanes) {
const updateQueue: UpdateQueue<any> = workInProgress.updateQueue as any;
workInProgress.memoizedState = updateQueue.baseState = derivedState;
}

pushCacheProvider(workInProgress, parentCache);
// No need to propagate a context change because the refreshed parent
// already did.
// 不需要传播上下文的变化,因为刷新后的父对象已经处理过了。
} else {
// The parent didn't refresh. Now check if this cache did.
// 父类没有刷新。现在检查这个缓存是否刷新了。
const nextCache = nextState.cache;
pushCacheProvider(workInProgress, nextCache);
if (nextCache !== prevState.cache) {
// This cache refreshed. Propagate a context change.
// 此缓存已刷新。传播上下文更改。
propagateContextChange(workInProgress, CacheContext, renderLanes);
}
}
}

const nextProps: CacheProps = workInProgress.pendingProps;

const nextChildren = nextProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

15. 更新追踪标记组件

备注
信息

只有在名称更改时才应调用此方法

// This should only be called if the name changes
// 只有在名称更改时才应调用此方法
function updateTracingMarkerComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
if (!enableTransitionTracing) {
return null;
}

const nextProps: TracingMarkerProps = workInProgress.pendingProps;

// TODO: (luna) Only update the tracing marker if it's newly rendered or it's name changed.
// A tracing marker is only associated with the transitions that rendered
// or updated it, so we can create a new set of transitions each time
//
// 待办事项: (luna) 只有在追踪标记是新渲染的或其名称发生变化时才更新它。
// 追踪标记只与渲染或更新它的过渡相关联,
// 因此我们每次都可以创建一组新的过渡
if (current === null) {
const currentTransitions = getPendingTransitions();
if (currentTransitions !== null) {
const markerInstance: TracingMarkerInstance = {
tag: TransitionTracingMarker,
transitions: new Set(currentTransitions),
pendingBoundaries: null,
name: nextProps.name,
aborts: null,
};
workInProgress.stateNode = markerInstance;

// We call the marker complete callback when all child suspense boundaries resolve.
// We do this in the commit phase on Offscreen. If the marker has no child suspense
// boundaries, we need to schedule a passive effect to make sure we call the marker
// complete callback.
//
// 当所有子 suspense 边界解析完成时,我们会调用标记完成回调。
// 我们在 Offscreen 上的提交阶段执行此操作。如果标记没有子 suspense
// 边界,我们需要安排一个被动效果以确保调用标记完成回调。
workInProgress.flags |= Passive;
}
} else {
if (__DEV__) {
if (current.memoizedProps.name !== nextProps.name) {
console.error(
'Changing the name of a tracing marker after mount is not supported. ' +
'To remount the tracing marker, pass it a new key.',
);
}
}
}

const instance: TracingMarkerInstance | null = workInProgress.stateNode;
if (instance !== null) {
pushMarkerInstance(workInProgress, instance);
}
const nextChildren = nextProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

16. 更新片段

function updateFragment(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextChildren = workInProgress.pendingProps;
if (enableFragmentRefs) {
markRef(current, workInProgress);
}
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

17. 更新模式

function updateMode(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

18. 更新分析器

function updateProfiler(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
if (enableProfilerTimer) {
workInProgress.flags |= Update;

if (enableProfilerCommitHooks) {
// Schedule a passive effect for this Profiler to call onPostCommit hooks.
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
// because the effect is also where times bubble to parent Profilers.
//
// 为此分析器调度一个被动效果以调用 onPostCommit 钩子。
// 即使此分析器没有 onPostCommit 回调,也应调度此效果,
// 因为在此效果中时间也会冒泡到父分析器。
workInProgress.flags |= Passive;
// Reset effect durations for the next eventual effect phase.
// These are reset during render to allow the DevTools commit hook a chance to read them,
//
// 为下一个可能的效果阶段重置效果持续时间。
// 在渲染期间会重置这些,以便 DevTools 提交钩子有机会读取它们,
const stateNode = workInProgress.stateNode;
stateNode.effectDuration = -0;
stateNode.passiveEffectDuration = -0;
}
}
const nextProps: ProfilerProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

19. 标记引用

function markRef(current: Fiber | null, workInProgress: Fiber) {
// TODO: Check props.ref instead of fiber.ref when enableRefAsProp is on.
// TODO:当启用 enableRefAsProp 时,应检查 props.ref 而不是 fiber.ref。
const ref = workInProgress.ref;
if (ref === null) {
if (current !== null && current.ref !== null) {
// Schedule a Ref effect
// 调度 Ref 效果
workInProgress.flags |= Ref | RefStatic;
}
} else {
if (typeof ref !== 'function' && typeof ref !== 'object') {
throw new Error(
'Expected ref to be a function, an object returned by React.createRef(), or undefined/null.',
);
}
if (current === null || current.ref !== ref) {
// Schedule a Ref effect
// 调度 Ref 效果
workInProgress.flags |= Ref | RefStatic;
}
}
}

20. 挂载不完整的函数组件

function mountIncompleteFunctionComponent(
_current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);

workInProgress.tag = FunctionComponent;

return updateFunctionComponent(
null,
workInProgress,
Component,
nextProps,
renderLanes,
);
}

21. 更新函数组件

备注
function updateFunctionComponent(
current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
if (__DEV__) {
if (
Component.prototype &&
typeof Component.prototype.render === 'function'
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';

if (!didWarnAboutBadClass[componentName]) {
console.error(
"The <%s /> component appears to have a render method, but doesn't extend React.Component. " +
'This is likely to cause errors. Change %s to extend React.Component instead.',
componentName,
componentName,
);
didWarnAboutBadClass[componentName] = true;
}
}

if (workInProgress.mode & StrictLegacyMode) {
ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
}

if (current === null) {
// Some validations were previously done in mountIndeterminateComponent however and are now run
// in updateFuntionComponent but only on mount
//
// 一些验证以前是在 mountIndeterminateComponent 中完成的,但现在在 updateFuntionComponent 中
// 运行,只在挂载时执行
validateFunctionComponentInDev(workInProgress, workInProgress.type);

if (Component.contextTypes) {
const componentName = getComponentNameFromType(Component) || 'Unknown';

if (!didWarnAboutContextTypes[componentName]) {
didWarnAboutContextTypes[componentName] = true;
if (disableLegacyContext) {
console.error(
'%s uses the legacy contextTypes API which was removed in React 19. ' +
'Use React.createContext() with React.useContext() instead. ' +
'(https://react.dev/link/legacy-context)',
componentName,
);
} else {
console.error(
'%s uses the legacy contextTypes API which will be removed soon. ' +
'Use React.createContext() with React.useContext() instead. ' +
'(https://react.dev/link/legacy-context)',
componentName,
);
}
}
}
}
}

let context;
if (!disableLegacyContext && !disableLegacyContextForFunctionComponents) {
const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
context = getMaskedContext(workInProgress, unmaskedContext);
}

let nextChildren;
let hasId;
prepareToReadContext(workInProgress, renderLanes);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
} else {
nextChildren = renderWithHooks(
current,
workInProgress,
Component,
nextProps,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}

if (current !== null && !didReceiveUpdate) {
bailoutHooks(current, workInProgress, renderLanes);
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

if (getIsHydrating() && hasId) {
pushMaterializedTreeId(workInProgress);
}

// React DevTools reads this flag.
// React DevTools 会读取此标志。
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

22. 更新类组件

备注
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
if (__DEV__) {
// This is used by DevTools to force a boundary to error.
// 这用于开发工具强制边界出现错误。
switch (shouldError(workInProgress)) {
case false: {
const instance = workInProgress.stateNode;
const ctor = workInProgress.type;
// TODO This way of resetting the error boundary state is a hack.
// Is there a better way to do this?
//
// TODO 这种重置错误边界状态的方式是一个hack。
// 有没有更好的方法来做这件事?
const tempInstance = new ctor(
workInProgress.memoizedProps,
instance.context,
);
const state = tempInstance.state;
instance.updater.enqueueSetState(instance, state, null);
break;
}
case true: {
workInProgress.flags |= DidCapture;
workInProgress.flags |= ShouldCapture;
const error = new Error('Simulated error coming from DevTools');
const lane = pickArbitraryLane(renderLanes);
workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
// Schedule the error boundary to re-render using updated state
// 使用更新后的状态安排错误边界重新渲染
const root: FiberRoot | null = getWorkInProgressRoot();
if (root === null) {
throw new Error(
'Expected a work-in-progress root. This is a bug in React. Please file an issue.',
);
}
const update = createClassErrorUpdate(lane);
initializeClassErrorUpdate(
update,
root,
workInProgress,
createCapturedValueAtFiber(error, workInProgress),
);
enqueueCapturedUpdate(workInProgress, update);
break;
}
}
}

// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
//
// 及早推送上下文提供者以防止上下文堆栈不匹配。
// 在挂载期间,我们还不知道子上下文,因为实例尚不存在。
// 我们将在渲染后立即在 finishClassComponent() 中使子上下文失效。
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderLanes);

const instance = workInProgress.stateNode;
let shouldUpdate;
if (instance === null) {
resetSuspendedCurrentOnMountInLegacyMode(current, workInProgress);

// In the initial pass we might need to construct the instance.
// 在初始阶段,我们可能需要构建该实例。
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
// 在简历中,我们已经有一个可以重复使用的实例。
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderLanes,
);
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderLanes,
);
}
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
if (__DEV__) {
const inst = workInProgress.stateNode;
if (shouldUpdate && inst.props !== nextProps) {
if (!didWarnAboutReassigningProps) {
console.error(
'It looks like %s is reassigning its own `this.props` while rendering. ' +
'This is not supported and can lead to confusing bugs.',
getComponentNameFromFiber(workInProgress) || 'a component',
);
}
didWarnAboutReassigningProps = true;
}
}
return nextUnitOfWork;
}

23. 完成类组件

备注
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderLanes: Lanes,
) {
// Refs should update even if shouldComponentUpdate returns false
// 即使 shouldComponentUpdate 返回 false,refs 也应该更新
markRef(current, workInProgress);

const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;

if (!shouldUpdate && !didCaptureError) {
// Context providers should defer to sCU for rendering
// 上下文提供者在渲染时应参考 shouldComponentUpdate
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}

return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}

const instance = workInProgress.stateNode;

// Rerender
// 重新渲染
if (__DEV__) {
setCurrentFiber(workInProgress);
}
let nextChildren;
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
// If we captured an error, but getDerivedStateFromError is not defined,
// unmount all the children. componentDidCatch will schedule an update to
// re-render a fallback. This is temporary until we migrate everyone to
// the new API.
// TODO: Warn in a future release.
//
// 如果我们捕获了一个错误,但没有定义 getDerivedStateFromError,
// 卸载所有子组件。componentDidCatch 会安排一次更新以
// 重新渲染一个备用界面。这是暂时的,直到我们将所有人迁移到
// 新的 API。
// TODO: 在未来的版本中发出警告。
nextChildren = null;

if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
} else {
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
nextChildren = callRenderInDEV(instance);
if (workInProgress.mode & StrictLegacyMode) {
setIsStrictModeForDevtools(true);
try {
callRenderInDEV(instance);
} finally {
setIsStrictModeForDevtools(false);
}
}
} else {
nextChildren = instance.render();
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}
}

// React DevTools reads this flag.
// React DevTools 会读取此标志。
workInProgress.flags |= PerformedWork;
if (current !== null && didCaptureError) {
// If we're recovering from an error, reconcile without reusing any of
// the existing children. Conceptually, the normal children and the children
// that are shown on error are two different sets, so we shouldn't reuse
// normal children even if their identities match.
//
// 如果我们正在从错误中恢复,不要重用任何现有的子元素进行协调。
// 从概念上讲,正常的子元素和错误时显示的子元素是两个不同的集合,
// 因此即使它们的身份匹配,我们也不应该重用正常的子元素。
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderLanes,
);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}

// Memoize state using the values we just used to render.
// TODO: Restructure so we never read values from the instance.
//
// 使用我们刚用来渲染的值来记忆化状态。
// TODO:重构以便我们永远不从实例中读取值。
workInProgress.memoizedState = instance.state;

// The context might have changed so we need to recalculate it.
// 上下文可能已经改变,所以我们需要重新计算它。
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}

return workInProgress.child;
}

24. 推送宿主环境根上下文

备注
function pushHostRootContext(workInProgress: Fiber) {
const root = workInProgress.stateNode as FiberRoot;
if (root.pendingContext) {
pushTopLevelContextObject(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context,
);
} else if (root.context) {
// Should always be set
// 应始终设置
pushTopLevelContextObject(workInProgress, root.context, false);
}
pushHostContainer(workInProgress, root.containerInfo);
}

25. 更新宿主环境根

备注
function updateHostRoot(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostRootContext(workInProgress);

if (current === null) {
throw new Error('Should have a current fiber. This is a bug in React.');
}

const nextProps = workInProgress.pendingProps;
const prevState: RootState = workInProgress.memoizedState;
const prevChildren = prevState.element;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);

const nextState: RootState = workInProgress.memoizedState;
const root: FiberRoot = workInProgress.stateNode;
pushRootTransition(workInProgress, root, renderLanes);

if (enableTransitionTracing) {
pushRootMarkerInstance(workInProgress);
}

const nextCache: Cache = nextState.cache;
pushCacheProvider(workInProgress, nextCache);
if (nextCache !== prevState.cache) {
// The root cache refreshed.
// 根缓存已刷新。
propagateContextChange(workInProgress, CacheContext, renderLanes);
}

// This would ideally go inside processUpdateQueue, but because it suspends,
// it needs to happen after the `pushCacheProvider` call above to avoid a
// context stack mismatch. A bit unfortunate.
//
// 这本来理想情况下应该放在 processUpdateQueue 内部,但因为它会挂起,
// 所以需要在上面的 `pushCacheProvider` 调用之后进行,以避免
// 上下文堆栈不匹配。有点不太理想。
suspendIfUpdateReadFromEntangledAsyncAction();

// Caution: React DevTools currently depends on this property
// being called "element".
//
// 注意:React 开发者工具目前依赖于该属性被称为“element”。
const nextChildren = nextState.element;
if (supportsHydration && prevState.isDehydrated) {
// This is a hydration root whose shell has not yet hydrated. We should
// attempt to hydrate.
//
// 这是一个尚未完成水合的根节点。我们应该尝试进行水合。

// Flip isDehydrated to false to indicate that when this render
// finishes, the root will no longer be dehydrated.
//
// 将 isDehydrated 翻转为 false,以表示当此渲染完成后,根节点将不再是脱水状态。
const overrideState: RootState = {
element: nextChildren,
isDehydrated: false,
cache: nextState.cache,
};
const updateQueue: UpdateQueue<RootState> =
workInProgress.updateQueue as any;
// `baseState` can always be the last state because the root doesn't
// have reducer functions so it doesn't need rebasing.
//
// `baseState` 总是可以作为最后一个状态,因为根节点没有 reducer 函数,所以不需要重新基准化。
updateQueue.baseState = overrideState;
workInProgress.memoizedState = overrideState;

if (workInProgress.flags & ForceClientRender) {
// Something errored during a previous attempt to hydrate the shell, so we
// forced a client render. We should have a recoverable error already scheduled.
//
// 在之前尝试为 shell 进行 hydration 时出错,因此我们
// 强制进行了客户端渲染。我们应该已经安排了一个可恢复的错误。
return mountHostRootWithoutHydrating(
current,
workInProgress,
nextChildren,
renderLanes,
);
} else if (nextChildren !== prevChildren) {
const recoverableError = createCapturedValueAtFiber<mixed>(
new Error(
'This root received an early update, before anything was able ' +
'hydrate. Switched the entire root to client rendering.',
),
workInProgress,
);
queueHydrationError(recoverableError);
return mountHostRootWithoutHydrating(
current,
workInProgress,
nextChildren,
renderLanes,
);
} else {
// The outermost shell has not hydrated yet. Start hydrating.
// 最外层的壳还未完成水合。开始水合。
enterHydrationState(workInProgress);

const child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
workInProgress.child = child;

let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
//
// 将每个子节点标记为正在水合。这是一条快速路径,用于判断该树是否是水合树的一部分。它
// 用于确定子节点是否已经完全挂载,以及用于调度事件重放。概念上,这类似于 Placement,因为
// 新的子树在这里被插入到 React 树中。只是碰巧不需要 DOM 变更,因为它已经存在。
node.flags = (node.flags & ~Placement) | Hydrating;
node = node.sibling;
}
}
} else {
// Root is not dehydrated. Either this is a client-only root, or it
// already hydrated.
//
// 根节点未被脱水。要么这是一个仅客户端的根节点,要么它已经被水合。
resetHydrationState();
if (nextChildren === prevChildren) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}

26. 在不执行水合的情况下挂载宿主环境根节点

备注
function mountHostRootWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
nextChildren: ReactNodeList,
renderLanes: Lanes,
) {
// Revert to client rendering.
// 恢复客户端渲染。
resetHydrationState();

workInProgress.flags |= ForceClientRender;

reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

27. 更新宿主环境组件

备注
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}

pushHostContext(workInProgress);

const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;

let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);

if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also has access to this prop. That
// avoids allocating another HostText fiber and traversing it.
//
// 我们对宿主环境节点的直接文本子节点做特殊处理。这是一个常见情况。
// 我们不会将其作为具体化的子节点来处理。我们会在同样可以访问该属性的宿主环境环境中处理它。
// 这样就避免了分配另一个 HostText fiber 并遍历它。
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
//
// 如果我们从直接的文本子节点切换到普通子节点,或者切换为空,我们需要安排重置文本内容。
workInProgress.flags |= ContentReset;
}

const memoizedState = workInProgress.memoizedState;
if (memoizedState !== null) {
// This fiber has been upgraded to a stateful component. The only way
// happens currently is for form actions. We use hooks to track the
// pending and error state of the form.
//
// 这个 fiber 已经被升级为有状态组件。目前唯一发生这种情况的是表单操作。我们
// 使用钩子来跟踪表单的挂起状态和错误状态。
//
// Once a fiber is upgraded to be stateful, it remains stateful for the
// rest of its lifetime.
//
// 一旦一个 fiber 被升级为有状态,它在其余生命周期中将保持有状态。
const newState = renderTransitionAwareHostComponentWithHooks(
current,
workInProgress,
renderLanes,
);

// If the transition state changed, propagate the change to all the
// descendents. We use Context as an implementation detail for this.
//
// 如果过渡状态发生了变化,将此变化传播到所有子节点。
// 我们使用 Context 作为实现细节来处理这一点。
//
// We need to update it here because
// pushHostContext gets called before we process the state hook, to avoid
// a state mismatch in the event that something suspends.
//
// 这里故意设置在此,而不是在 pushHostContext 中设置,因为
// pushHostContext 会在我们处理状态钩子之前被调用,以避免
// 在某些操作挂起时发生状态不匹配。
//
// NOTE: This assumes that there cannot be nested transition providers,
// because the only renderer that implements this feature is React DOM,
// and forms cannot be nested. If we did support nested providers, then
// we would need to push a context value even for host fibers that
// haven't been upgraded yet.
//
// 注意:这假设不能有嵌套的过渡提供者,
// 因为唯一实现此功能的渲染器是 React DOM,
// 且表单不能嵌套。如果我们确实支持嵌套提供者,
// 那么即使对于尚未升级的宿主 fiber,我们也需要推送一个上下文值。
if (isPrimaryRenderer) {
HostTransitionContext._currentValue = newState;
} else {
HostTransitionContext._currentValue2 = newState;
}
}

markRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

28. 更新宿主环境可提升性

备注
function updateHostHoistable(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
markRef(current, workInProgress);

if (current === null) {
const resource = getResource(
workInProgress.type,
null,
workInProgress.pendingProps,
null,
);
if (resource) {
workInProgress.memoizedState = resource;
} else {
if (!getIsHydrating()) {
// This is not a Resource Hoistable and we aren't hydrating so we construct the instance.
// 这不是可提升的资源,并且我们没有进行水合操作,所以我们直接构建实例。
workInProgress.stateNode = createHoistableInstance(
workInProgress.type,
workInProgress.pendingProps,
getRootHostContainer(),
workInProgress,
);
}
}
} else {
// Get Resource may or may not return a resource. either way we stash the result
// on memoized state.
//
// 获取资源可能会返回也可能不会返回资源。无论哪种情况,我们都会将结果存储在已备忘的状态中。
workInProgress.memoizedState = getResource(
workInProgress.type,
current.memoizedProps,
workInProgress.pendingProps,
current.memoizedState,
);
}

// Resources never have reconciler managed children. It is possible for
// the host implementation of getResource to consider children in the
// resource construction but they will otherwise be discarded. In practice
// this precludes all but the simplest children and Host specific warnings
// should be implemented to warn when children are passsed when otherwise not
// expected
//
// 资源永远不会有调和器管理的子节点。宿主实现的 getResource 方法在资源构建时可能会考虑子节
// 点,但它们会被丢弃。实际上,这排除了除了最简单子节点之外的所有情况,应当实现宿主特定的警
// 告,当传递了本不应有的子节点时给予提示。
return null;
}

29. 更新宿主环境单例

备注
function updateHostSingleton(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContext(workInProgress);

if (current === null) {
claimHydratableSingleton(workInProgress);
}

const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
markRef(current, workInProgress);
if (current === null) {
// We mark Singletons with a static flag to more efficiently manage their
// ownership of the singleton host instance when in offscreen trees including Suspense
//
// 我们使用静态标志来标记单例,以便在离屏树(包括 Suspense)中更高效地管理它们对单例宿主实例的所有权
workInProgress.flags |= LayoutStatic;
}
return workInProgress.child;
}

30. 更新宿主环境文本

备注
function updateHostText(current: null | Fiber, workInProgress: Fiber) {
if (current === null) {
tryToClaimNextHydratableTextInstance(workInProgress);
}
// Nothing to do here. This is terminal. We'll do the completion step
// immediately after.
//
// 这里没什么可做的。这是终端。我们会紧接着进行完成步骤。
return null;
}

31. 挂载懒加载组件

备注
function mountLazyComponent(
_current: null | Fiber,
workInProgress: Fiber,
elementType: any,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);

const props = workInProgress.pendingProps;
const lazyComponent: LazyComponentType<any, any> = elementType;
let Component = resolveLazy(lazyComponent);
// Store the unwrapped component in the type.
// 将未封装的组件存储在类型中。
workInProgress.type = Component;

if (typeof Component === 'function') {
if (isFunctionClassComponent(Component)) {
const resolvedProps = resolveClassComponentProps(Component, props);
workInProgress.tag = ClassComponent;
if (__DEV__) {
workInProgress.type = Component =
resolveClassForHotReloading(Component);
}
return updateClassComponent(
null,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
} else {
workInProgress.tag = FunctionComponent;
if (__DEV__) {
validateFunctionComponentInDev(workInProgress, Component);
workInProgress.type = Component =
resolveFunctionForHotReloading(Component);
}
return updateFunctionComponent(
null,
workInProgress,
Component,
props,
renderLanes,
);
}
} else if (Component !== undefined && Component !== null) {
const $$typeof = Component.$$typeof;
if ($$typeof === REACT_FORWARD_REF_TYPE) {
workInProgress.tag = ForwardRef;
if (__DEV__) {
workInProgress.type = Component =
resolveForwardRefForHotReloading(Component);
}
return updateForwardRef(
null,
workInProgress,
Component,
props,
renderLanes,
);
} else if ($$typeof === REACT_MEMO_TYPE) {
workInProgress.tag = MemoComponent;
return updateMemoComponent(
null,
workInProgress,
Component,
props,
renderLanes,
);
}
}

let hint = '';
if (__DEV__) {
if (
Component !== null &&
typeof Component === 'object' &&
Component.$$typeof === REACT_LAZY_TYPE
) {
hint = ' Did you wrap a component in React.lazy() more than once?';
}
}

const loggedComponent = getComponentNameFromType(Component) || Component;

// This message intentionally doesn't mention ForwardRef or MemoComponent
// because the fact that it's a separate type of work is an
// implementation detail.
//
// 这条信息故意没有提到 ForwardRef 或 MemoComponent
// 因为它作为一种独立工作类型的事实
// 是一个实现细节。
throw new Error(
`Element type is invalid. Received a promise that resolves to: ${loggedComponent}. ` +
`Lazy element type must resolve to a class or function.${hint}`,
);
}

32. 挂载不完整的类组件

备注
function mountIncompleteClassComponent(
_current: null | Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
resetSuspendedCurrentOnMountInLegacyMode(_current, workInProgress);

// Promote the fiber to a class and try rendering again.
// 将 fiber 升级为类并重试渲染。
workInProgress.tag = ClassComponent;

// The rest of this function is a fork of `updateClassComponent`
// 这个函数的其余部分是 `updateClassComponent` 的一个分支

// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
//
// 及早推送上下文提供者以防止上下文堆栈不匹配。
// 在挂载阶段,我们还不知道子上下文,因为实例尚不存在。
// 我们将在渲染后立即在 finishClassComponent() 中使子上下文失效。
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderLanes);

constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);

return finishClassComponent(
null,
workInProgress,
Component,
true,
hasContext,
renderLanes,
);
}

33. 在开发中验证函数组件

function validateFunctionComponentInDev(workInProgress: Fiber, Component: any) {
if (__DEV__) {
if (Component && Component.childContextTypes) {
console.error(
'childContextTypes cannot be defined on a function component.\n' +
' %s.childContextTypes = ...',
Component.displayName || Component.name || 'Component',
);
}

if (typeof Component.getDerivedStateFromProps === 'function') {
const componentName = getComponentNameFromType(Component) || 'Unknown';

if (!didWarnAboutGetDerivedStateOnFunctionComponent[componentName]) {
console.error(
'%s: Function components do not support getDerivedStateFromProps.',
componentName,
);
didWarnAboutGetDerivedStateOnFunctionComponent[componentName] = true;
}
}

if (
typeof Component.contextType === 'object' &&
Component.contextType !== null
) {
const componentName = getComponentNameFromType(Component) || 'Unknown';

if (!didWarnAboutContextTypeOnFunctionComponent[componentName]) {
console.error(
'%s: Function components do not support contextType.',
componentName,
);
didWarnAboutContextTypeOnFunctionComponent[componentName] = true;
}
}
}
}

34. 挂载挂起离屏状态

备注
function mountSuspenseOffscreenState(renderLanes: Lanes): OffscreenState {
return {
baseLanes: renderLanes,
cachePool: getSuspendedCache(),
};
}

35. 更新暂挂离屏状态

备注
function updateSuspenseOffscreenState(
prevOffscreenState: OffscreenState,
renderLanes: Lanes,
): OffscreenState {
let cachePool: SpawnedCachePool | null = null;
const prevCachePool: SpawnedCachePool | null = prevOffscreenState.cachePool;
if (prevCachePool !== null) {
const parentCache = isPrimaryRenderer
? CacheContext._currentValue
: CacheContext._currentValue2;
if (prevCachePool.parent !== parentCache) {
// Detected a refresh in the parent. This overrides any previously
// suspended cache.
//
// 检测到父级刷新。这会覆盖之前挂起的缓存。
cachePool = {
parent: parentCache,
pool: parentCache,
};
} else {
// We can reuse the cache from last time. The only thing that would have
// overridden it is a parent refresh, which we checked for above.
//
// 我们可以重用上次的缓存。唯一可能覆盖它的是父级刷新,我们在上面已经检查过。
cachePool = prevCachePool;
}
} else {
// If there's no previous cache pool, grab the current one.
// 如果没有以前的缓存池,就获取当前的缓存池。
cachePool = getSuspendedCache();
}
return {
baseLanes: mergeLanes(prevOffscreenState.baseLanes, renderLanes),
cachePool,
};
}

36. 应该保持在备用状态

备注
// TODO: Probably should inline this back
// 待办:可能应该把这个内联回来
function shouldRemainOnFallback(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// If we're already showing a fallback, there are cases where we need to
// remain on that fallback regardless of whether the content has resolved.
// For example, SuspenseList coordinates when nested content appears.
// TODO: For compatibility with offscreen prerendering, this should also check
// whether the current fiber (if it exists) was visible in the previous tree.
//
// 如果我们已经显示了一个回退,有些情况下我们需要
// 无论内容是否已解决,都保持显示该回退。
// 例如,SuspenseList 会协调嵌套内容何时出现。
// TODO:为了与离屏预渲染兼容,这里还应该检查
// 当前的 fiber(如果存在)在之前的树中是否可见。
if (current !== null) {
const suspenseState: SuspenseState = current.memoizedState;
if (suspenseState === null) {
// Currently showing content. Don't hide it, even if ForceSuspenseFallback
// is true. More precise name might be "ForceRemainSuspenseFallback".
// Note: This is a factoring smell. Can't remain on a fallback if there's
// no fallback to remain on.
//
// 当前正在显示内容。即使 ForceSuspenseFallback 为 true,也不要隐藏它。
// 更准确的名称可能是 “ForceRemainSuspenseFallback”。
// 注意:这是一个重构上的异味。如果没有备用内容可以显示,就无法停留在备用内容上。
return false;
}
}

// Not currently showing content. Consult the Suspense context.
// 当前未显示内容。请查看 Suspense 上下文。
const suspenseContext: SuspenseContext = suspenseStackCursor.current;
return hasSuspenseListContext(
suspenseContext,
ForceSuspenseFallback as SuspenseContext,
);
}

37. 获取主树中剩余的工作

备注
function getRemainingWorkInPrimaryTree(
current: Fiber | null,
primaryTreeDidDefer: boolean,
renderLanes: Lanes,
) {
let remainingLanes =
current !== null ? removeLanes(current.childLanes, renderLanes) : NoLanes;
if (primaryTreeDidDefer) {
// A useDeferredValue hook spawned a deferred task inside the primary tree.
// Ensure that we retry this component at the deferred priority.
// TODO: We could make this a per-subtree value instead of a global one.
// Would need to track it on the context stack somehow, similar to what
// we'd have to do for resumable contexts.
//
// useDeferredValue 钩子在主树内生成了一个延迟任务。
// 确保我们以延迟优先级重试此组件。
// 待办事项:我们可以将其改为每个子树的值,而不是全局值。
// 需要以某种方式在上下文栈上跟踪它,类似于我们为可恢复上下文所做的处理。
remainingLanes = mergeLanes(remainingLanes, peekDeferredLane());
}
return remainingLanes;
}

38. 更新挂起组件

备注
function updateSuspenseComponent(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps: SuspenseProps = workInProgress.pendingProps;

// This is used by DevTools to force a boundary to suspend.
// 这用于开发工具强制边界挂起。
if (__DEV__) {
if (shouldSuspend(workInProgress)) {
workInProgress.flags |= DidCapture;
}
}

let showFallback = false;
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
if (
didSuspend ||
shouldRemainOnFallback(current, workInProgress, renderLanes)
) {
// Something in this boundary's subtree already suspended. Switch to
// rendering the fallback children.
//
// 这个边界的子树中已有内容被挂起。切换渲染备用子节点。
showFallback = true;
workInProgress.flags &= ~DidCapture;
}

// Check if the primary children spawned a deferred task (useDeferredValue)
// during the first pass.
//
// 检查主要子组件在第一次渲染时是否生成了延迟任务(useDeferredValue)
const didPrimaryChildrenDefer = (workInProgress.flags & DidDefer) !== NoFlags;
workInProgress.flags &= ~DidDefer;

// OK, the next part is confusing. We're about to reconcile the Suspense
// boundary's children. This involves some custom reconciliation logic. Two
// main reasons this is so complicated.
//
// 好的,接下来的部分有点复杂。我们正准备协调 Suspense 边界的子元素。这涉及一些自定义的
// 协调逻辑。有两个主要原因使得这很复杂。
//
// First, Legacy Mode has different semantics for backwards compatibility. The
// primary tree will commit in an inconsistent state, so when we do the
// second pass to render the fallback, we do some exceedingly, uh, clever
// hacks to make that not totally break. Like transferring effects and
// deletions from hidden tree. In Concurrent Mode, it's much simpler,
// because we bailout on the primary tree completely and leave it in its old
// state, no effects. Same as what we do for Offscreen (except that
// Offscreen doesn't have the first render pass).
//
// 首先,传统模式为了向后兼容具有不同的语义。主树会在不一致的状态下提交,因此当我们进行
// 第二遍来渲染备用内容时,我们会使用一些非常聪明的技巧来避免完全崩溃。比如从隐藏树中转
// 移副作用和删除。在并发模式下,就简单得多了,因为我们会完全跳过主树并保持它的旧状态,没
// 有副作用。和我们对离屏处理的方式一样(只是离屏没有第一遍渲染)。
//
// Second is hydration. During hydration, the Suspense fiber has a slightly
// different layout, where the child points to a dehydrated fragment, which
// contains the DOM rendered by the server.
//
// 第二步是水合。在水合过程中,Suspense fiber 的布局略有不同,子节点指向一个
// 脱水的片段,该片段包含服务器渲染的 DOM。
//
// Third, even if you set all that aside, Suspense is like error boundaries in
// that we first we try to render one tree, and if that fails, we render again
// and switch to a different tree. Like a try/catch block. So we have to track
// which branch we're currently rendering. Ideally we would model this using
// a stack.
//
// 第三,即使你把所有这些都放一边,Suspense 有点像错误边界,
// 我们首先尝试渲染一棵树,如果失败,我们就重新渲染
// 并切换到另一棵树。就像一个 try/catch 块。所以我们必须跟踪
// 当前正在渲染的分支。理想情况下,我们会使用栈来建模这个过程。
if (current === null) {
// Initial mount
// 初始挂载

// Special path for hydration
// If we're currently hydrating, try to hydrate this boundary.
//
// 用于 hydration 的特殊路径
// 如果我们当前正在进行 hydration,尝试为此边界进行 hydration。
if (getIsHydrating()) {
// We must push the suspense handler context *before* attempting to
// hydrate, to avoid a mismatch in case it errors.
//
// 我们必须在尝试水合之前先推送 suspense 处理器上下文,以防它出错导致不匹配。
if (showFallback) {
pushPrimaryTreeSuspenseHandler(workInProgress);
} else {
pushFallbackTreeSuspenseHandler(workInProgress);
}
// This throws if we fail to hydrate.
// 如果我们未能初始化,则会抛出此异常。
const dehydrated: SuspenseInstance =
claimNextHydratableSuspenseInstance(workInProgress);
return mountDehydratedSuspenseComponent(
workInProgress,
dehydrated,
renderLanes,
);
}

const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;

if (showFallback) {
pushFallbackTreeSuspenseHandler(workInProgress);

mountSuspenseFallbackChildren(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes,
);
const primaryChildFragment: Fiber = workInProgress.child as any;
primaryChildFragment.memoizedState =
mountSuspenseOffscreenState(renderLanes);
primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
current,
didPrimaryChildrenDefer,
renderLanes,
);
workInProgress.memoizedState = SUSPENDED_MARKER;
if (enableTransitionTracing) {
const currentTransitions = getPendingTransitions();
if (currentTransitions !== null) {
const parentMarkerInstances = getMarkerInstances();
const offscreenQueue: OffscreenQueue | null =
primaryChildFragment.updateQueue as any;
if (offscreenQueue === null) {
const newOffscreenQueue: OffscreenQueue = {
transitions: currentTransitions,
markerInstances: parentMarkerInstances,
retryQueue: null,
};
primaryChildFragment.updateQueue = newOffscreenQueue;
} else {
offscreenQueue.transitions = currentTransitions;
offscreenQueue.markerInstances = parentMarkerInstances;
}
}
}

return bailoutOffscreenComponent(null, primaryChildFragment);
} else if (enableCPUSuspense && nextProps.defer === true) {
// This is a CPU-bound tree. Skip this tree and show a placeholder to
// unblock the surrounding content. Then immediately retry after the
// initial commit.
//
// 这是一个 CPU 密集型树。跳过此树并显示一个占位符以
// 解除周围内容的阻塞。然后在初始提交后立即重试。
pushFallbackTreeSuspenseHandler(workInProgress);
mountSuspenseFallbackChildren(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes,
);
const primaryChildFragment: Fiber = workInProgress.child as any;
primaryChildFragment.memoizedState =
mountSuspenseOffscreenState(renderLanes);
primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
current,
didPrimaryChildrenDefer,
renderLanes,
);
workInProgress.memoizedState = SUSPENDED_MARKER;

// TODO: Transition Tracing is not yet implemented for CPU Suspense.
// 待办:CPU 暂停模式下的过渡追踪尚未实现。

// 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 操作是一样的。我们可以使用任何 RetryLane,即使它是当前正在渲染
// 的那个 lane,因为我们会在这个节点上将它留下。
workInProgress.lanes = SomeRetryLane;
return bailoutOffscreenComponent(null, primaryChildFragment);
} else {
pushPrimaryTreeSuspenseHandler(workInProgress);
return mountSuspensePrimaryChildren(
workInProgress,
nextPrimaryChildren,
renderLanes,
);
}
} else {
// This is an update.
// 这是一次更新。

// Special path for hydration
// 用于水合的特殊路径
const prevState: null | SuspenseState = current.memoizedState;
if (prevState !== null) {
const dehydrated = prevState.dehydrated;
if (dehydrated !== null) {
return updateDehydratedSuspenseComponent(
current,
workInProgress,
didSuspend,
didPrimaryChildrenDefer,
nextProps,
dehydrated,
prevState,
renderLanes,
);
}
}

if (showFallback) {
pushFallbackTreeSuspenseHandler(workInProgress);

const nextFallbackChildren = nextProps.fallback;
const nextPrimaryChildren = nextProps.children;
updateSuspenseFallbackChildren(
current,
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes,
);
const primaryChildFragment: Fiber = workInProgress.child as any;
const prevOffscreenState: OffscreenState | null = (current.child as any)
.memoizedState;
primaryChildFragment.memoizedState =
prevOffscreenState === null
? mountSuspenseOffscreenState(renderLanes)
: updateSuspenseOffscreenState(prevOffscreenState, renderLanes);
if (enableTransitionTracing) {
const currentTransitions = getPendingTransitions();
if (currentTransitions !== null) {
const parentMarkerInstances = getMarkerInstances();
const offscreenQueue: OffscreenQueue | null =
primaryChildFragment.updateQueue as any;
const currentOffscreenQueue: OffscreenQueue | null =
current.updateQueue as any;
if (offscreenQueue === null) {
const newOffscreenQueue: OffscreenQueue = {
transitions: currentTransitions,
markerInstances: parentMarkerInstances,
retryQueue: null,
};
primaryChildFragment.updateQueue = newOffscreenQueue;
} else if (offscreenQueue === currentOffscreenQueue) {
// If the work-in-progress queue is the same object as current, we
// can't modify it without cloning it first.
//
// 如果正在进行的队列与当前队列是同一个对象,我们
// 在修改它之前必须先克隆它。
const newOffscreenQueue: OffscreenQueue = {
transitions: currentTransitions,
markerInstances: parentMarkerInstances,
retryQueue:
currentOffscreenQueue !== null
? currentOffscreenQueue.retryQueue
: null,
};
primaryChildFragment.updateQueue = newOffscreenQueue;
} else {
offscreenQueue.transitions = currentTransitions;
offscreenQueue.markerInstances = parentMarkerInstances;
}
}
}
primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
current,
didPrimaryChildrenDefer,
renderLanes,
);
workInProgress.memoizedState = SUSPENDED_MARKER;
return bailoutOffscreenComponent(current.child, primaryChildFragment);
} else {
if (
prevState !== null &&
includesOnlyRetries(renderLanes) &&
includesSomeLane(renderLanes, current.lanes)
) {
// If we're rendering Retry lanes and we're entering the primary content then it's possible
// that the only reason we rendered was because we left this boundary to be warmed up but
// nothing else scheduled an update. If so, use it as the cause of the render.
//
// 如果我们正在渲染重试通道并且正在进入主内容,那么有可能
// 我们渲染的唯一原因是我们离开了这个边界以便预热,但
// 没有其他东西安排更新。如果是这样,就将其作为渲染的原因。
markRenderDerivedCause(workInProgress);
}

pushPrimaryTreeSuspenseHandler(workInProgress);

const nextPrimaryChildren = nextProps.children;
const primaryChildFragment = updateSuspensePrimaryChildren(
current,
workInProgress,
nextPrimaryChildren,
renderLanes,
);
workInProgress.memoizedState = null;
return primaryChildFragment;
}
}
}

39. 挂载挂起主要子组件

function mountSuspensePrimaryChildren(
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = workInProgress.mode;
const primaryChildProps: OffscreenProps = {
mode: 'visible',
children: primaryChildren,
};
const primaryChildFragment = mountWorkInProgressOffscreenFiber(
primaryChildProps,
mode,
renderLanes,
);
primaryChildFragment.return = workInProgress;
workInProgress.child = primaryChildFragment;
return primaryChildFragment;
}

40. 挂载暂缓回退子组件

备注
function mountSuspenseFallbackChildren(
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = workInProgress.mode;
const progressedPrimaryFragment: Fiber | null = workInProgress.child;

const primaryChildProps: OffscreenProps = {
mode: 'hidden',
children: primaryChildren,
};

let primaryChildFragment;
let fallbackChildFragment;
if (
!disableLegacyMode &&
(mode & ConcurrentMode) === NoMode &&
progressedPrimaryFragment !== null
) {
// In legacy mode, we commit the primary tree as if it successfully
// completed, even though it's in an inconsistent state.
//
// 在传统模式下,即使主树处于不一致状态,我们也会像它已成功完成一样提交主树。
primaryChildFragment = progressedPrimaryFragment;
primaryChildFragment.childLanes = NoLanes;
primaryChildFragment.pendingProps = primaryChildProps;

if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
// Reset the durations from the first pass so they aren't included in the
// final amounts. This seems counterintuitive, since we're intentionally
// not measuring part of the render phase, but this makes it match what we
// do in Concurrent Mode.
//
// 重置第一次测量的持续时间,以免它们被计入最终数值。
// 这看起来有些反直觉,因为我们有意不测量渲染阶段的一部分,
// 但这样可以使其与我们在并发模式中的做法一致。
primaryChildFragment.actualDuration = -0;
primaryChildFragment.actualStartTime = -1.1;
primaryChildFragment.selfBaseDuration = -0;
primaryChildFragment.treeBaseDuration = -0;
}

fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
renderLanes,
null,
);
} else {
primaryChildFragment = mountWorkInProgressOffscreenFiber(
primaryChildProps,
mode,
NoLanes,
);
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
renderLanes,
null,
);
}

primaryChildFragment.return = workInProgress;
fallbackChildFragment.return = workInProgress;
primaryChildFragment.sibling = fallbackChildFragment;
workInProgress.child = primaryChildFragment;
return fallbackChildFragment;
}

41. 挂载离屏进行中的 Fiber

备注
  • createFiberFromOffscreen()ReactFiber 提供
function mountWorkInProgressOffscreenFiber(
offscreenProps: OffscreenProps,
mode: TypeOfMode,
renderLanes: Lanes,
) {
// The props argument to `createFiberFromOffscreen` is `any` typed, so we use
// this wrapper function to constrain it.
//
// `createFiberFromOffscreen` 的 props 参数类型是 `any`,所以我们使用这个包装函数来约束它。
return createFiberFromOffscreen(offscreenProps, mode, NoLanes, null);
}

42. 更新离屏进行中的 Fiber

备注
function updateWorkInProgressOffscreenFiber(
current: Fiber,
offscreenProps: OffscreenProps,
) {
// The props argument to `createWorkInProgress` is `any` typed, so we use this
// wrapper function to constrain it.
//
// 传给 `createWorkInProgress` 的 props 参数类型是 `any`,所以我们使用这个包装函数来约束它。
return createWorkInProgress(current, offscreenProps);
}

43. 更新挂起的主要子节点

function updateSuspensePrimaryChildren(
current: Fiber,
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const currentPrimaryChildFragment: Fiber = current.child as any;
const currentFallbackChildFragment: Fiber | null =
currentPrimaryChildFragment.sibling;

const primaryChildFragment = updateWorkInProgressOffscreenFiber(
currentPrimaryChildFragment,
{
mode: 'visible',
children: primaryChildren,
},
);
if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
primaryChildFragment.lanes = renderLanes;
}
primaryChildFragment.return = workInProgress;
primaryChildFragment.sibling = null;
if (currentFallbackChildFragment !== null) {
// Delete the fallback child fragment
// 删除备用子片段
const deletions = workInProgress.deletions;
if (deletions === null) {
workInProgress.deletions = [currentFallbackChildFragment];
workInProgress.flags |= ChildDeletion;
} else {
deletions.push(currentFallbackChildFragment);
}
}

workInProgress.child = primaryChildFragment;
return primaryChildFragment;
}

44. 更新挂起回退子组件

备注
function updateSuspenseFallbackChildren(
current: Fiber,
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const mode = workInProgress.mode;
const currentPrimaryChildFragment: Fiber = current.child as any;
const currentFallbackChildFragment: Fiber | null =
currentPrimaryChildFragment.sibling;

const primaryChildProps: OffscreenProps = {
mode: 'hidden',
children: primaryChildren,
};

let primaryChildFragment;
if (
// In legacy mode, we commit the primary tree as if it successfully
// completed, even though it's in an inconsistent state.
//
// 在传统模式下,即使主树处于不一致状态,我们也会像它已成功完成一样提交主树。
!disableLegacyMode &&
(mode & ConcurrentMode) === NoMode &&
// Make sure we're on the second pass, i.e. the primary child fragment was
// already cloned. In legacy mode, the only case where this isn't true is
// when DevTools forces us to display a fallback; we skip the first render
// pass entirely and go straight to rendering the fallback. (In Concurrent
// Mode, SuspenseList can also trigger this scenario, but this is a legacy-
// only codepath.)
//
// 确保我们处于第二次遍历,也就是主要的子片段已经被克隆。在旧模式下,唯一不满足这一点的情况
// 是当 DevTools 强制我们显示备用内容时;我们会完全跳过第一次渲染遍历,直接渲染备用内容。
// (在并发模式下,SuspenseList 也可能触发这种情况,但这是仅用于旧版本的代码路径。)
workInProgress.child !== currentPrimaryChildFragment
) {
const progressedPrimaryFragment: Fiber = workInProgress.child as any;
primaryChildFragment = progressedPrimaryFragment;
primaryChildFragment.childLanes = NoLanes;
primaryChildFragment.pendingProps = primaryChildProps;

if (enableProfilerTimer && workInProgress.mode & ProfileMode) {
// Reset the durations from the first pass so they aren't included in the
// final amounts. This seems counterintuitive, since we're intentionally
// not measuring part of the render phase, but this makes it match what we
// do in Concurrent Mode.
//
// 重置第一次测量的持续时间,以免它们被计入最终数值。这似乎有些违反直觉,因为我们刻意不去
// 测量渲染阶段的某一部分,但这样可以使其与我们在并发模式下的做法一致。
primaryChildFragment.actualDuration = -0;
primaryChildFragment.actualStartTime = -1.1;
primaryChildFragment.selfBaseDuration =
currentPrimaryChildFragment.selfBaseDuration;
primaryChildFragment.treeBaseDuration =
currentPrimaryChildFragment.treeBaseDuration;
}

// The fallback fiber was added as a deletion during the first pass.
// However, since we're going to remain on the fallback, we no longer want
// to delete it.
//
// 回退 fiber 在第一次遍历时被添加为删除操作。
// 但是,由于我们将继续使用回退 fiber,因此我们不再希望删除它。
workInProgress.deletions = null;
} else {
primaryChildFragment = updateWorkInProgressOffscreenFiber(
currentPrimaryChildFragment,
primaryChildProps,
);
// Since we're reusing a current tree, we need to reuse the flags, too.
// (We don't do this in legacy mode, because in legacy mode we don't re-use
// the current tree; see previous branch.)
//
// 由于我们正在重用当前的树,我们也需要重用标记。
//(在传统模式下我们不这样做,因为在传统模式下我们不重用
// 当前的树;参见之前的分支。)
primaryChildFragment.subtreeFlags =
currentPrimaryChildFragment.subtreeFlags & StaticMask;
}
let fallbackChildFragment;
if (currentFallbackChildFragment !== null) {
fallbackChildFragment = createWorkInProgress(
currentFallbackChildFragment,
fallbackChildren,
);
} else {
fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
mode,
renderLanes,
null,
);
// Needs a placement effect because the parent (the Suspense boundary) already
// mounted but this is a new fiber.
//
// 需要一个占位效果,因为父节点(Suspense 边界)已经挂载,但这是一个新的 fiber。
fallbackChildFragment.flags |= Placement;
}

fallbackChildFragment.return = workInProgress;
primaryChildFragment.return = workInProgress;
primaryChildFragment.sibling = fallbackChildFragment;
workInProgress.child = primaryChildFragment;

return bailoutOffscreenComponent(null, primaryChildFragment);
}

45. 在不进行水合的情况下重试挂起组件

备注
function retrySuspenseComponentWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// Falling back to client rendering. Because this has performance
// implications, it's considered a recoverable error, even though the user
// likely won't observe anything wrong with the UI.
//
// 回退到客户端渲染。由于这会影响性能,虽然用户可能不会察觉
// 到界面有任何异常,但它仍被视为可恢复错误。

// This will add the old fiber to the deletion list
// 这将把旧的 fiber 添加到删除列表中
reconcileChildFibers(workInProgress, current.child, null, renderLanes);

// We're now not suspended nor dehydrated.
// 我们现在既没有悬浮也没有脱水。
const nextProps = workInProgress.pendingProps;
const primaryChildren = nextProps.children;
const primaryChildFragment = mountSuspensePrimaryChildren(
workInProgress,
primaryChildren,
renderLanes,
);
// Needs a placement effect because the parent (the Suspense boundary) already
// mounted but this is a new fiber.
//
// 需要一个占位效果,因为父节点(Suspense 边界)已经挂载,但这是一个新的 fiber。
primaryChildFragment.flags |= Placement;
workInProgress.memoizedState = null;

return primaryChildFragment;
}

46. 在重试后挂载 Suspense 回退而不进行水合

备注
function mountSuspenseFallbackAfterRetryWithoutHydrating(
current: Fiber,
workInProgress: Fiber,
primaryChildren: $FlowFixMe,
fallbackChildren: $FlowFixMe,
renderLanes: Lanes,
) {
const fiberMode = workInProgress.mode;
const primaryChildProps: OffscreenProps = {
mode: 'visible',
children: primaryChildren,
};
const primaryChildFragment = mountWorkInProgressOffscreenFiber(
primaryChildProps,
fiberMode,
NoLanes,
);
const fallbackChildFragment = createFiberFromFragment(
fallbackChildren,
fiberMode,
renderLanes,
null,
);
// Needs a placement effect because the parent (the Suspense
// boundary) already mounted but this is a new fiber.
//
// 需要一个放置效果,因为父节点(Suspense 边界)已经挂载,但这是一个新的 fiber。
fallbackChildFragment.flags |= Placement;

primaryChildFragment.return = workInProgress;
fallbackChildFragment.return = workInProgress;
primaryChildFragment.sibling = fallbackChildFragment;
workInProgress.child = primaryChildFragment;

if (disableLegacyMode || (workInProgress.mode & ConcurrentMode) !== NoMode) {
// We will have dropped the effect list which contains the
// deletion. We need to reconcile to delete the current child.
//
// 我们将删除包含删除操作的效果列表。我们需要进行协调以删除当前子项。
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
}

return fallbackChildFragment;
}

47. 挂载脱水挂起组件

备注
function mountDehydratedSuspenseComponent(
workInProgress: Fiber,
suspenseInstance: SuspenseInstance,
renderLanes: Lanes,
): null | Fiber {
// During the first pass, we'll bail out and not drill into the children.
// Instead, we'll leave the content in place and try to hydrate it later.
//
// 在第一次遍历时,我们会中途退出,而不会深入到子节点。
// 相反,我们会保留内容原样,并尝试稍后进行水合。
if (isSuspenseInstanceFallback(suspenseInstance)) {
// This is a client-only boundary. Since we won't get any content from the server
// for this, we need to schedule that at a higher priority based on when it would
// have timed out. In theory we could render it in this pass but it would have the
// wrong priority associated with it and will prevent hydration of parent path.
// Instead, we'll leave work left on it to render it in a separate commit.
// Schedule a normal pri update to render this content.
//
// 这是一个仅限客户端的边界。由于我们无法从服务器获取任何内容
// 对于这个,我们需要根据它可能超时的时间以更高的优先级来安排它。
// 理论上我们可以在这一轮渲染中渲染它,但它的优先级会不正确,
// 并且会阻止父路径的水合。
// 相反,我们会保留一些工作,让它在一个单独的提交中渲染。
// 安排一个普通优先级的更新来渲染此内容。
workInProgress.lanes = laneToLanes(
enableHydrationLaneScheduling ? DefaultLane : DefaultHydrationLane,
);
} else {
// We'll continue hydrating the rest at offscreen priority since we'll already
// be showing the right content coming from the server, it is no rush.
//
// 我们将继续以离屏优先级对其余部分进行水合,因为我们已经会显示来自服务器的正确内容,所以不急。
workInProgress.lanes = laneToLanes(OffscreenLane);
}
return null;
}

48. 更新脱水挂起组件

备注
function updateDehydratedSuspenseComponent(
current: Fiber,
workInProgress: Fiber,
didSuspend: boolean,
didPrimaryChildrenDefer: boolean,
nextProps: SuspenseProps,
suspenseInstance: SuspenseInstance,
suspenseState: SuspenseState,
renderLanes: Lanes,
): null | Fiber {
if (!didSuspend) {
// This is the first render pass. Attempt to hydrate.
// 这是第一次渲染。尝试进行水合。
pushPrimaryTreeSuspenseHandler(workInProgress);

// We should never be hydrating at this point because it is the first pass,
// but after we've already committed once.
//
// 在这一点上我们绝不应该水合,因为这是第一次遍历,
// 但在我们已经提交过一次之后可以这样做。
warnIfHydrating();

if (includesSomeLane(renderLanes, OffscreenLane as Lane)) {
// If we're rendering Offscreen and we're entering the activity then it's possible
// that the only reason we rendered was because this boundary left work. Provide
// it as a cause if another one doesn't already exist.
//
// 如果我们在离屏渲染并且正在进入活动,那么渲染的唯一原因可能是因为这个边界完成了工作。如果还
// 没有其他原因存在,就把它作为一个原因提供。
markRenderDerivedCause(workInProgress);
}

if (isSuspenseInstanceFallback(suspenseInstance)) {
// This boundary is in a permanent fallback state. In this case, we'll never
// get an update and we'll never be able to hydrate the final content. Let's just try the
// client side render instead.
//
// 这个边界处于永久回退状态。在这种情况下,我们永远不会收到更新,也
// 无法填充最终内容。我们还是尝试客户端渲染吧。
let digest: ?string;
let message;
let stack = null;
let componentStack = null;
if (__DEV__) {
({ digest, message, stack, componentStack } =
getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
} else {
({ digest } =
getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
}

let error: Error;
if (__DEV__ && message) {
error = new Error(message);
} else {
error = new Error(
'The server could not finish this Suspense boundary, likely ' +
'due to an error during server rendering. ' +
'Switched to client rendering.',
);
}
// Replace the stack with the server stack
// 将堆栈替换为服务器堆栈
error.stack = (__DEV__ && stack) || '';
(error as any).digest = digest;
const capturedValue = createCapturedValueFromError(
error,
componentStack === undefined ? null : componentStack,
);
queueHydrationError(capturedValue);
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
);
}

if (
// TODO: Factoring is a little weird, since we check this right below, too.
// 待办事项:因式分解有点奇怪,因为我们也在下面检查了这个。
!didReceiveUpdate
) {
// We need to check if any children have context before we decide to bail
// out, so propagate the changes now.
//
// 我们需要检查是否有子节点有上下文,然后再决定是否退出,
// 所以现在传播这些更改。
lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
}

// We use lanes to indicate that a child might depend on context, so if
// any context has changed, we need to treat is as if the input might have changed.
//
// 我们使用 lanes 来表示孩子可能依赖于上下文,所以如果任何上下文发生了变化,我们需要将
// 其视为输入可能已经改变。
const hasContextChanged = includesSomeLane(renderLanes, current.childLanes);
if (didReceiveUpdate || hasContextChanged) {
// This boundary has changed since the first render. This means that we are now unable to
// hydrate it. We might still be able to hydrate it using a higher priority lane.
//
// 自第一次渲染以来,此边界已更改。这意味着我们现在无法对其进行 hydration。
// 我们仍然可能能够使用更高优先级的通道对其进行 hydration。
const root = getWorkInProgressRoot();
if (root !== null) {
const attemptHydrationAtLane = getBumpedLaneForHydration(
root,
renderLanes,
);
if (
attemptHydrationAtLane !== NoLane &&
attemptHydrationAtLane !== suspenseState.retryLane
) {
// Intentionally mutating since this render will get interrupted. This
// is one of the very rare times where we mutate the current tree
// during the render phase.
//
// 故意进行变更,因为这个渲染会被中断。
// 这是在渲染阶段改变当前树的极少数情况之一。
suspenseState.retryLane = attemptHydrationAtLane;
enqueueConcurrentRenderForLane(current, attemptHydrationAtLane);
scheduleUpdateOnFiber(root, current, attemptHydrationAtLane);

// Throw a special object that signals to the work loop that it should
// interrupt the current render.
//
// 抛出一个特殊对象,向工作循环发出信号,表示它应该中断当前渲染。
//
// Because we're inside a React-only execution stack, we don't
// strictly need to throw here — we could instead modify some internal
// work loop state. But using an exception means we don't need to
// check for this case on every iteration of the work loop. So doing
// it this way moves the check out of the fast path.
//
// 因为我们处在一个仅用于 React 的执行栈中,所以我们并不严格需要在这里抛出异常——我
// 们可以选择修改一些内部的工作循环状态。但使用异常意味着我们不需要在每次工作循环迭代
// 时都检查这种情况。所以以这种方式处理会将检查移出快速路径。
throw SelectiveHydrationException;
} else {
// We have already tried to ping at a higher priority than we're rendering with
// so if we got here, we must have failed to hydrate at those levels. We must
// now give up. Instead, we're going to delete the whole subtree and instead inject
// a new real Suspense boundary to take its place, which may render content
// or fallback. This might suspend for a while and if it does we might still have
// an opportunity to hydrate before this pass commits.
//
// 我们已经尝试以比我们渲染的优先级更高的优先级进行 ping
// 所以如果我们走到了这里,说明我们在这些级别上水合失败了。
// 现在我们必须放弃。相反,我们将删除整个子树,
// 并注入一个新的真实 Suspense 边界来替代它,
// 这个边界可能渲染内容或回退。这可能会暂停一段时间,
// 如果发生这种情况,我们仍然有机会在此次处理提交之前进行水合。
}
}

// If we did not selectively hydrate, we'll continue rendering without
// hydrating. Mark this tree as suspended to prevent it from committing
// outside a transition.
//
// 如果我们没有选择性地进行 hydration,我们将继续渲染而不进行 hydration。将此树
// 标记为挂起以防止它在过渡之外提交。
//
// This path should only happen if the hydration lane already suspended.
// 只有在 hydration lane 已经挂起时才会发生此路径。
if (isSuspenseInstancePending(suspenseInstance)) {
// This is a dehydrated suspense instance. We don't need to suspend
// because we're already showing a fallback.
// TODO: The Fizz runtime might still stream in completed HTML, out-of-
// band. Should we fix this? There's a version of this bug that happens
// during client rendering, too. Needs more consideration.
//
// 这是一个脱水的 suspense 实例。我们不需要挂起
// 因为我们已经显示了一个回退。
// TODO: Fizz 运行时可能仍会流式传输已完成的 HTML,
// 这是异步的。我们是否应该修复这个问题?
// 这个错误的一个版本也会在客户端渲染时发生。
// 需要更多的考虑。
} else {
renderDidSuspendDelayIfPossible();
}
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
);
} else if (isSuspenseInstancePending(suspenseInstance)) {
// This component is still pending more data from the server, so we can't hydrate its
// content. We treat it as if this component suspended itself. It might seem as if
// we could just try to render it client-side instead. However, this will perform a
// lot of unnecessary work and is unlikely to complete since it often will suspend
// on missing data anyway. Additionally, the server might be able to render more
// than we can on the client yet. In that case we'd end up with more fallback states
// on the client than if we just leave it alone. If the server times out or errors
// these should update this boundary to the permanent Fallback state instead.
// Mark it as having captured (i.e. suspended).
// Also Mark it as requiring retry.
//
// 这个组件仍在等待来自服务器的更多数据,所以我们无法对其内容进行 hydration。我们将其视为该组件
// 自行挂起。看起来我们似乎可以尝试在客户端渲染它,但这样会执行大量不必要的工作,而且由于它通常会
// 因为数据缺失而挂起,所以不太可能完成。此外,服务器可能可以渲染的内容比客户端多。在这种情况下,如
// 果我们不去处理的话,客户端的回退状态会比直接保留它更多。如果服务器超时或出错,这些边界应该更新为
// 永久回退状态。
// 标记为已捕获(即已挂起)。
// 还要标记为需要重试。
workInProgress.flags |= DidCapture | Callback;
// Leave the child in place. I.e. the dehydrated fragment.
// 保持子项原位,即脱水的片段。
workInProgress.child = current.child;
return null;
} else {
// This is the first attempt.
// 这是第一次尝试。
reenterHydrationStateFromDehydratedSuspenseInstance(
workInProgress,
suspenseInstance,
suspenseState.treeContext,
);
const primaryChildren = nextProps.children;
const primaryChildFragment = mountSuspensePrimaryChildren(
workInProgress,
primaryChildren,
renderLanes,
);
// Mark the children as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
//
// 将子节点标记为正在进行 hydration(数据恢复)。这是快速判断该树是否属于正在 hydration 的树的方法。
// 这用于确定子节点是否已经完全挂载,以及调度事件重放。
// 从概念上讲,这类似于 Placement,因为新的子树会被插入到 React 树中。
// 只是恰好不需要 DOM 变更,因为它已经存在。
primaryChildFragment.flags |= Hydrating;
return primaryChildFragment;
}
} else {
// This is the second render pass. We already attempted to hydrated, but
// something either suspended or errored.
//
// 这是第二次渲染。我们已经尝试过进行 hydration,但
// 某些地方要么挂起了,要么出错了。

if (workInProgress.flags & ForceClientRender) {
// Something errored during hydration. Try again without hydrating.
// The error should've already been logged in throwException.
//
// 在水合过程中出现了错误。请尝试在不进行水合的情况下再次操作。
// 错误应该已经在 throwException 中记录过了。
pushPrimaryTreeSuspenseHandler(workInProgress);
workInProgress.flags &= ~ForceClientRender;
return retrySuspenseComponentWithoutHydrating(
current,
workInProgress,
renderLanes,
);
} else if (
(workInProgress.memoizedState as null | SuspenseState) !== null
) {
// Something suspended and we should still be in dehydrated mode.
// Leave the existing child in place.
//
// 某些东西被挂起了,我们仍然应该处于脱水模式。
// 保持现有的子项不变。

// Push to avoid a mismatch
// 强制推送以避免不匹配
pushFallbackTreeSuspenseHandler(workInProgress);

workInProgress.child = current.child;
// The dehydrated completion pass expects this flag to be there
// but the normal suspense pass doesn't.
//
// 脱水完成阶段预计会有这个标志
// 但普通的 suspense 阶段则不需要。
workInProgress.flags |= DidCapture;
return null;
} else {
// Suspended but we should no longer be in dehydrated mode.
// Therefore we now have to render the fallback.
//
// 已暂停,但我们不应再处于脱水模式。
// 因此我们现在必须渲染备用内容。
pushFallbackTreeSuspenseHandler(workInProgress);

const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;
mountSuspenseFallbackAfterRetryWithoutHydrating(
current,
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes,
);
const primaryChildFragment: Fiber = workInProgress.child as any;
primaryChildFragment.memoizedState =
mountSuspenseOffscreenState(renderLanes);
primaryChildFragment.childLanes = getRemainingWorkInPrimaryTree(
current,
didPrimaryChildrenDefer,
renderLanes,
);
workInProgress.memoizedState = SUSPENDED_MARKER;
return bailoutOffscreenComponent(null, primaryChildFragment);
}
}
}

49. 在 Fiber 上调度挂起工作

备注
function scheduleSuspenseWorkOnFiber(
fiber: Fiber,
renderLanes: Lanes,
propagationRoot: Fiber,
) {
fiber.lanes = mergeLanes(fiber.lanes, renderLanes);
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, renderLanes);
}
scheduleContextWorkOnParentPath(fiber.return, renderLanes, propagationRoot);
}

50. 传播挂起上下文更改

function propagateSuspenseContextChange(
workInProgress: Fiber,
firstChild: null | Fiber,
renderLanes: Lanes,
): void {
// Mark any Suspense boundaries with fallbacks as having work to do.
// If they were previously forced into fallbacks, they may now be able
// to unblock.
//
// 将任何带有回退的 Suspense 边界标记为有工作要做。
// 如果它们之前被强制进入回退,现在可能能够解除阻塞。
let node = firstChild;
while (node !== null) {
if (node.tag === SuspenseComponent) {
const state: SuspenseState | null = node.memoizedState;
if (state !== null) {
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
}
} else if (node.tag === SuspenseListComponent) {
// If the tail is hidden there might not be an Suspense boundaries
// to schedule work on. In this case we have to schedule it on the
// list itself.
// We don't have to traverse to the children of the list since
// the list will propagate the change when it rerenders.
//
// 如果尾部被隐藏,可能就没有可调度工作的 Suspense 边界。
// 在这种情况下,我们必须在列表本身上调度它。
// 我们不必遍历列表的子组件,因为列表在重新渲染时会传播更改。
scheduleSuspenseWorkOnFiber(node, renderLanes, workInProgress);
} 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;
}
}

51. 找到最后内容行

备注
function findLastContentRow(firstChild: null | Fiber): null | Fiber {
// This is going to find the last row among these children that is already
// showing content on the screen, as opposed to being in fallback state or
// new. If a row has multiple Suspense boundaries, any of them being in the
// fallback state, counts as the whole row being in a fallback state.
// Note that the "rows" will be workInProgress, but any nested children
// will still be current since we haven't rendered them yet. The mounted
// order may not be the same as the new order. We use the new order.
//
// 这将找到这些子元素中最后一行已经在屏幕上显示内容的行,而不是处于回退状态或新的行。
// 如果一行有多个 Suspense 边界,其中任何一个处于回退状态,都算作整行处于回退状态。
// 注意,这里的“行”是 workInProgress,但任何嵌套的子元素仍然是 current,因为我们
// 还没有渲染它们。挂载顺序可能与新顺序不同。我们使用新顺序。
let row = firstChild;
let lastContentRow: null | Fiber = null;
while (row !== null) {
const currentRow = row.alternate;
// New rows can't be content rows.
// 新行不能是内容行。
if (currentRow !== null && findFirstSuspended(currentRow) === null) {
lastContentRow = row;
}
row = row.sibling;
}
return lastContentRow;
}

52. 验证揭示顺序

function validateRevealOrder(revealOrder: SuspenseListRevealOrder) {
if (__DEV__) {
const cacheKey = revealOrder == null ? 'null' : revealOrder;
if (
revealOrder != null &&
revealOrder !== 'forwards' &&
revealOrder !== 'backwards' &&
revealOrder !== 'unstable_legacy-backwards' &&
revealOrder !== 'together' &&
revealOrder !== 'independent' &&
!didWarnAboutRevealOrder[cacheKey]
) {
didWarnAboutRevealOrder[cacheKey] = true;
if (typeof revealOrder === 'string') {
switch (revealOrder.toLowerCase()) {
case 'together':
case 'forwards':
case 'backwards':
case 'independent': {
console.error(
'"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
'Use lowercase "%s" instead.',
revealOrder,
revealOrder.toLowerCase(),
);
break;
}
case 'forward':
case 'backward': {
console.error(
'"%s" is not a valid value for revealOrder on <SuspenseList />. ' +
'React uses the -s suffix in the spelling. Use "%ss" instead.',
revealOrder,
revealOrder.toLowerCase(),
);
break;
}
default:
console.error(
'"%s" is not a supported revealOrder on <SuspenseList />. ' +
'Did you mean "independent", "together", "forwards" or "backwards"?',
revealOrder,
);
break;
}
} else {
console.error(
'%s is not a supported value for revealOrder on <SuspenseList />. ' +
'Did you mean "independent", "together", "forwards" or "backwards"?',
revealOrder,
);
}
}
}
}

53. 验证尾部选项

function validateTailOptions(
tailMode: SuspenseListTailMode,
revealOrder: SuspenseListRevealOrder,
) {
if (__DEV__) {
const cacheKey = tailMode == null ? 'null' : tailMode;
if (!didWarnAboutTailOptions[cacheKey]) {
if (tailMode == null) {
// The default tail is now "hidden".
// 默认尾部现在是“隐藏”的。
} else if (
tailMode !== 'visible' &&
tailMode !== 'collapsed' &&
tailMode !== 'hidden'
) {
didWarnAboutTailOptions[cacheKey] = true;
console.error(
'"%s" is not a supported value for tail on <SuspenseList />. ' +
'Did you mean "visible", "collapsed" or "hidden"?',
tailMode,
);
} else if (
revealOrder != null &&
revealOrder !== 'forwards' &&
revealOrder !== 'backwards' &&
revealOrder !== 'unstable_legacy-backwards'
) {
didWarnAboutTailOptions[cacheKey] = true;
console.error(
'<SuspenseList tail="%s" /> is only valid if revealOrder is ' +
'"forwards" (default) or "backwards". ' +
'Did you mean to specify revealOrder="forwards"?',
tailMode,
);
}
}
}
}

54. 初始化Suspense列表渲染状态

function initSuspenseListRenderState(
workInProgress: Fiber,
isBackwards: boolean,
tail: null | Fiber,
lastContentRow: null | Fiber,
tailMode: SuspenseListTailMode,
treeForkCount: number,
): void {
const renderState: null | SuspenseListRenderState =
workInProgress.memoizedState;
if (renderState === null) {
workInProgress.memoizedState = ({
isBackwards: isBackwards,
rendering: null,
renderingStartTime: 0,
last: lastContentRow,
tail: tail,
tailMode: tailMode,
treeForkCount: treeForkCount,
}: SuspenseListRenderState);
} else {
// We can reuse the existing object from previous renders.
// 我们可以重复使用之前渲染中已有的对象。
renderState.isBackwards = isBackwards;
renderState.rendering = null;
renderState.renderingStartTime = 0;
renderState.last = lastContentRow;
renderState.tail = tail;
renderState.tailMode = tailMode;
renderState.treeForkCount = treeForkCount;
}
}

55. 反转子元素

function reverseChildren(fiber: Fiber): void {
let row = fiber.child;
fiber.child = null;
while (row !== null) {
const nextRow = row.sibling;
row.sibling = fiber.child;
fiber.child = row;
row = nextRow;
}
}

56. 更新挂起列表组件

备注
信息

这可能会导致这个组 件渲染多次 。第一次渲染将子 Fiber 分成两组:头部和尾部。我们首先渲染头部。如果有任何部分处于回退状态,我们会再通过 beginWork 进行一次渲染,使用强制挂起上下文重新渲染所有子节点(包括尾部)。如果第一次渲染时没有任何部分处于回退状态,那么我们会一次渲染尾部的每一行。这会在 completeWork 阶段完成,而不需要返回 beginWork

// This can end up rendering this component multiple passes.
// The first pass splits the children fibers into two sets. A head and tail.
// We first render the head. If anything is in fallback state, we do another
// pass through beginWork to rerender all children (including the tail) with
// the force suspend context. If the first render didn't have anything in
// in fallback state. Then we render each row in the tail one-by-one.
// That happens in the completeWork phase without going back to beginWork.
function updateSuspenseListComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps: SuspenseListProps = workInProgress.pendingProps;
const revealOrder: SuspenseListRevealOrder = nextProps.revealOrder;
const tailMode: SuspenseListTailMode = nextProps.tail;
const newChildren = nextProps.children;

let suspenseContext: SuspenseContext = suspenseStackCursor.current;

if (workInProgress.flags & DidCapture) {
// This is the second pass after having suspended in a row. Proceed directly
// to the complete phase.
//
// 这是在连续挂起后的第二次处理。直接进入完整阶段。
pushSuspenseListContext(workInProgress, suspenseContext);
return null;
}

const shouldForceFallback = hasSuspenseListContext(
suspenseContext,
ForceSuspenseFallback as SuspenseContext,
);
if (shouldForceFallback) {
suspenseContext = setShallowSuspenseListContext(
suspenseContext,
ForceSuspenseFallback,
);
workInProgress.flags |= DidCapture;
} else {
suspenseContext = setDefaultShallowSuspenseListContext(suspenseContext);
}
pushSuspenseListContext(workInProgress, suspenseContext);

validateRevealOrder(revealOrder);
validateTailOptions(tailMode, revealOrder);
validateSuspenseListChildren(newChildren, revealOrder);

if (revealOrder === 'backwards' && current !== null) {
// For backwards the current mounted set will be backwards. Reconciling against it
// will lead to mismatches and reorders. We need to swap the original set first
// and then restore it afterwards.
//
// 对于向后的操作,当前挂载的集合将是反向的。与其对账会导致不匹配和重新排序。
// 我们需要先交换原始集合,然后再恢复它。
reverseChildren(current);
reconcileChildren(current, workInProgress, newChildren, renderLanes);
reverseChildren(current);
} else {
reconcileChildren(current, workInProgress, newChildren, renderLanes);
}
// Read how many children forks this set pushed so we can push it every time we retry.
// 读取有多少子进程分叉了这个集合,以便我们每次重试时都能推送它。
const treeForkCount = getIsHydrating() ? getForksAtLevel(workInProgress) : 0;

if (!shouldForceFallback) {
const didSuspendBefore =
current !== null && (current.flags & DidCapture) !== NoFlags;
if (didSuspendBefore) {
// If we previously forced a fallback, we need to schedule work
// on any nested boundaries to let them know to try to render
// again. This is the same as context updating.
//
// 如果我们之前强制回退,我们需要安排工作
// 在任何嵌套边界上,让它们知道再次尝试渲染。
// 这与上下文更新是一样的。
propagateSuspenseContextChange(
workInProgress,
workInProgress.child,
renderLanes,
);
}
}

if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
// In legacy mode, SuspenseList doesn't work so we just
// use make it a noop by treating it as the default revealOrder.
//
// 在旧版模式下,SuspenseList 不起作用,所以我们把它当作默认的 revealOrder 来处
// 理,使其成为一个空操作。
workInProgress.memoizedState = null;
} else {
switch (revealOrder) {
case 'backwards': {
// We're going to find the first row that has existing content.
// We are also going to reverse the order of anything in the existing content
// since we want to actually render them backwards from the reconciled set.
// The tail is left in order, because it'll be added to the front as we
// complete each item.
//
// 我们将找到第一个有现有内容的行。
// 我们还将反转现有内容中的顺序
// 因为我们实际上希望从对账后的集合中反向渲染它们。
// 尾部保持原顺序,因为在完成每个项目时,它将被添加到前面。
const lastContentRow = findLastContentRow(workInProgress.child);
let tail;
if (lastContentRow === null) {
// The whole list is part of the tail.
// 整个列表都是尾部的一部分。
tail = workInProgress.child;
workInProgress.child = null;
} else {
// Disconnect the tail rows after the content row.
// We're going to render them separately later in reverse order.
//
// 在内容行之后断开尾部行。
// 我们稍后会以相反的顺序单独渲染它们。
tail = lastContentRow.sibling;
lastContentRow.sibling = null;
// We have to now reverse the main content so it renders backwards too.
// 我们现在必须反转主要内容,这样它也可以倒着显示。
reverseChildren(workInProgress);
}
// TODO: If workInProgress.child is null, we can continue on the tail immediately.
// 待办事项:如果 workInProgress.child 为 null,我们可以立即继续处理尾部。
initSuspenseListRenderState(
workInProgress,
// 是反向的
true, // isBackwards
tail,
// 最后
null, // last
tailMode,
treeForkCount,
);
break;
}
case 'unstable_legacy-backwards': {
// We're going to find the first row that has existing content.
// At the same time we're going to reverse the list of everything
// we pass in the meantime. That's going to be our tail in reverse
// order.
//
// 我们将找到第一个有内容的行。
// 与此同时,我们将反转传入的所有内容的列表。
// 这将成为我们以相反顺序的尾部。
let tail = null;
let row = workInProgress.child;
workInProgress.child = null;
while (row !== null) {
const currentRow = row.alternate;
// New rows can't be content rows.
// 新行不能是内容行。
if (currentRow !== null && findFirstSuspended(currentRow) === null) {
// This is the beginning of the main content.
// 这是主要内容的开始。
workInProgress.child = row;
break;
}
const nextRow = row.sibling;
row.sibling = tail;
tail = row;
row = nextRow;
}
// TODO: If workInProgress.child is null, we can continue on the tail immediately.
// 待办事项:如果 workInProgress.child 为 null,我们可以立即继续处理尾部。
initSuspenseListRenderState(
workInProgress,
// 是反向的
true, // isBackwards
tail,
// 最后
null, // last
tailMode,
treeForkCount,
);
break;
}
case 'together': {
initSuspenseListRenderState(
workInProgress,
// 是反向的
false, // isBackwards
// 尾部
null, // tail
// 最后
null, // last
undefined,
treeForkCount,
);
break;
}
case 'independent': {
// The "independent" reveal order is the same as not having
// a boundary.
//
// “独立”的显示顺序与没有边界时相同。
workInProgress.memoizedState = null;
break;
}
// The default is now forwards.
// 默认现在是向前。
case 'forwards':
default: {
const lastContentRow = findLastContentRow(workInProgress.child);
let tail;
if (lastContentRow === null) {
// The whole list is part of the tail.
// TODO: We could fast path by just rendering the tail now.
//
// 整个列表是尾部的一部分。
// TODO: 我们现在可以通过直接渲染尾部来加快处理速度。
tail = workInProgress.child;
workInProgress.child = null;
} else {
// Disconnect the tail rows after the content row.
// We're going to render them separately later.
//
// 在内容行之后断开尾部行。
// 我们稍后会单独渲染它们。
tail = lastContentRow.sibling;
lastContentRow.sibling = null;
}
initSuspenseListRenderState(
workInProgress,
// 是反向的
false, // isBackwards
tail,
lastContentRow,
tailMode,
treeForkCount,
);
break;
}
}
}
return workInProgress.child;
}

57. 更新视图过渡

备注
function updateViewTransition(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const pendingProps: ViewTransitionProps = workInProgress.pendingProps;
if (pendingProps.name != null && pendingProps.name !== 'auto') {
// Explicitly named boundary. We track it so that we can pair it up with another explicit
// boundary if we get deleted.
//
// 明确命名的边界。我们跟踪它,以便在被删除时可以将其与另一个明确的边界配对。
workInProgress.flags |=
current === null
? ViewTransitionNamedMount | ViewTransitionNamedStatic
: ViewTransitionNamedStatic;
} else {
// The server may have used useId to auto-assign a generated name for this boundary.
// We push a materialization to ensure child ids line up with the server.
//
// 服务器可能使用 useId 自动为此边界分配了一个生成的名称。
// 我们推动一次物化以确保子 ID 与服务器对齐。
if (getIsHydrating()) {
pushMaterializedTreeId(workInProgress);
}
}
if (__DEV__) {
if (pendingProps.className !== undefined) {
const example =
typeof pendingProps.className === 'string'
? JSON.stringify(pendingProps.className)
: '{...}';
if (!didWarnAboutClassNameOnViewTransition[example]) {
didWarnAboutClassNameOnViewTransition[example] = true;
console.error(
'<ViewTransition> doesn\'t accept a "className" prop. It has been renamed to "default".\n' +
'- <ViewTransition className=%s>\n' +
'+ <ViewTransition default=%s>',
example,
example,
);
}
}
}
if (current !== null && current.memoizedProps.name !== pendingProps.name) {
// If the name changes, we schedule a ref effect to create a new ref instance.
// 如果名字改变,我们会安排一个 ref effect 来创建一个新的 ref 实例。
workInProgress.flags |= Ref | RefStatic;
} else {
markRef(current, workInProgress);
}
const nextChildren = pendingProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

58. 更新门户组件

备注
function updatePortalComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
const nextChildren = workInProgress.pendingProps;
if (current === null) {
// Portals are special because we don't append the children during mount
// but at commit. Therefore we need to track insertions which the normal
// flow doesn't do during mount. This doesn't happen at the root because
// the root always starts with a "current" with a null child.
// TODO: Consider unifying this with how the root works.
//
// 门户是特殊的,因为我们在挂载时不会附加子元素
// 而是在提交时附加。因此我们需要跟踪插入操作,
// 这是正常流程在挂载期间不会做的。在根节点不会发生这种情况,
// 因为根节点总是以一个当前节点开始,其子节点为 null。
// TODO: 考虑将此与根节点的工作方式统一起来。
workInProgress.child = reconcileChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
return workInProgress.child;
}

59. 更新上下文提供者

备注
function updateContextProvider(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const context: ReactContext<any> = workInProgress.type;
const newProps = workInProgress.pendingProps;
const newValue = newProps.value;

if (__DEV__) {
if (!('value' in newProps)) {
if (!hasWarnedAboutUsingNoValuePropOnContextProvider) {
hasWarnedAboutUsingNoValuePropOnContextProvider = true;
console.error(
'The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?',
);
}
}
}

pushProvider(workInProgress, context, newValue);

const newChildren = newProps.children;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}

60. 更新上下文消费者

备注
function updateContextConsumer(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const consumerType: ReactConsumerType<any> = workInProgress.type;
const context: ReactContext<any> = consumerType._context;
const newProps = workInProgress.pendingProps;
const render = newProps.children;

if (__DEV__) {
if (typeof render !== 'function') {
console.error(
'A context consumer was rendered with multiple children, or a child ' +
"that isn't a function. A context consumer expects a single child " +
'that is a function. If you did pass a function, make sure there ' +
'is no trailing or leading whitespace around it.',
);
}
}

prepareToReadContext(workInProgress, renderLanes);
const newValue = readContext(context);
if (enableSchedulingProfiler) {
markComponentRenderStarted(workInProgress);
}
let newChildren;
if (__DEV__) {
newChildren = callComponentInDEV(render, newValue, undefined);
} else {
newChildren = render(newValue);
}
if (enableSchedulingProfiler) {
markComponentRenderStopped();
}

// React DevTools reads this flag.
// React DevTools 会读取此标志。
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, newChildren, renderLanes);
return workInProgress.child;
}

61. 更新范围组件

function updateScopeComponent(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
markRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}

62. 在旧模式下挂载时重置挂起的当前项

function resetSuspendedCurrentOnMountInLegacyMode(
current: null | Fiber,
workInProgress: Fiber,
) {
if (!disableLegacyMode && (workInProgress.mode & ConcurrentMode) === NoMode) {
if (current !== null) {
// A lazy component only mounts if it suspended inside a non-
// concurrent tree, in an inconsistent state. We want to treat it like
// a new mount, even though an empty version of it already committed.
// Disconnect the alternate pointers.
//
// 一个懒加载组件只有在非并发树中挂起时才会挂载,此时状态不一致。我们希望将其视为
// 新挂载,尽管其空版本已经提交过。断开备用指针。
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
// 由于这在概念上是一个新的 fiber,安排一个 Placement effect
workInProgress.flags |= Placement;
}
}
}

63. 撤销已完成的工作

备注
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (current !== null) {
// Reuse previous dependencies
// 复用之前的依赖
workInProgress.dependencies = current.dependencies;
}

if (enableProfilerTimer) {
// Don't update "base" render times for bailouts.
// 不要为意外退出更新“基础”渲染时间。
stopProfilerTimerIfRunning(workInProgress);
}

markSkippedUpdateLanes(workInProgress.lanes);

// Check if the children have any pending work.
// 检查子元素是否有未完成的工作。
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
// The children don't have any work either. We can skip them.
// TODO: Once we add back resuming, we should check if the children are
// a work-in-progress set. If so, we need to transfer their effects.
//
// 孩子们也没有任何工作。我们可以跳过他们。
// 待办事项:一旦我们重新添加恢复功能,我们应该检查孩子们是否是
// 一个进行中的工作集。如果是这样,我们需要转移他们的效果。

if (current !== null) {
// Before bailing out, check if there are any context changes in
// the children.
//
// 在退出之前,检查子元素中是否有任何上下文变化。
lazilyPropagateParentContextChanges(current, workInProgress, renderLanes);
if (!includesSomeLane(renderLanes, workInProgress.childLanes)) {
return null;
}
} else {
return null;
}
}

// This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue.
//
// 这个 fiber 本身没有工作,但它的子树有工作。克隆子 fiber 并继续。
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}

64. 重新挂载Fiber

function remountFiber(
current: Fiber,
oldWorkInProgress: Fiber,
newWorkInProgress: Fiber,
): Fiber | null {
if (__DEV__) {
const returnFiber = oldWorkInProgress.return;
if (returnFiber === null) {
throw new Error('Cannot swap the root fiber.');
}

// Disconnect from the old current.
// It will get deleted.
//
// 断开与旧连接的当前连接。
// 它将被删除。
current.alternate = null;
oldWorkInProgress.alternate = null;

// Connect to the new tree.
// 连接到新的树。
newWorkInProgress.index = oldWorkInProgress.index;
newWorkInProgress.sibling = oldWorkInProgress.sibling;
newWorkInProgress.return = oldWorkInProgress.return;
newWorkInProgress.ref = oldWorkInProgress.ref;

if (__DEV__) {
newWorkInProgress._debugInfo = oldWorkInProgress._debugInfo;
}

// Replace the child/sibling pointers above it.
// 替换上方的子节点/兄弟节点指针。
if (oldWorkInProgress === returnFiber.child) {
returnFiber.child = newWorkInProgress;
} else {
let prevSibling = returnFiber.child;
if (prevSibling === null) {
throw new Error('Expected parent to have a child.');
}
while (prevSibling.sibling !== oldWorkInProgress) {
prevSibling = prevSibling.sibling;
if (prevSibling === null) {
throw new Error('Expected to find the previous sibling.');
}
}
prevSibling.sibling = newWorkInProgress;
}

// Delete the old fiber and place the new one.
// Since the old fiber is disconnected, we have to schedule it manually.
//
// 删除旧的 fiber 并放置新的 fiber。
// 由于旧的 fiber 已断开连接,我们必须手动调度它。
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [current];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(current);
}

newWorkInProgress.flags |= Placement;

// Restart work from the new fiber.
// 从新的 fiber 重新开始工作。
return newWorkInProgress;
} else {
throw new Error(
'Did not expect this call in production. ' +
'This is a bug in React. Please file an issue.',
);
}
}

65. 检查计划更新或上下文

备注
function checkScheduledUpdateOrContext(
current: Fiber,
renderLanes: Lanes,
): boolean {
// Before performing an early bailout, we must check if there are pending
// updates or context.
//
// 在执行提前退出之前,我们必须检查是否有待处理的更新或上下文。
const updateLanes = current.lanes;
if (includesSomeLane(updateLanes, renderLanes)) {
return true;
}
// No pending update, but because context is propagated lazily, we need
// to check for a context change before we bail out.
//
// 没有待处理的更新,但由于上下文是延迟传播的,我们需要在退出之前检查上下文是否发生了变化。
const dependencies = current.dependencies;
if (dependencies !== null && checkIfContextChanged(dependencies)) {
return true;
}
return false;
}

66. 如果没有计划更新则尝试提前终止

备注
function attemptEarlyBailoutIfNoScheduledUpdate(
current: Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
//
// 这个 fiber 没有任何待处理的工作。在进入 begin 阶段之前直接退出。
// 在这个优化路径中,仍然有一些需要处理的记录工作,主要是将一些内容压入堆栈。
switch (workInProgress.tag) {
case HostRoot: {
pushHostRootContext(workInProgress);
const root: FiberRoot = workInProgress.stateNode;
pushRootTransition(workInProgress, root, renderLanes);

if (enableTransitionTracing) {
pushRootMarkerInstance(workInProgress);
}

const cache: Cache = current.memoizedState.cache;
pushCacheProvider(workInProgress, cache);
resetHydrationState();
break;
}
case HostSingleton:
case HostComponent:
pushHostContext(workInProgress);
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
const context: ReactContext<any> = workInProgress.type;
pushProvider(workInProgress, context, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
// Profiler should only call onRender when one of its descendants actually rendered.
// Profiler 应该仅在其某个子组件实际渲染时才调用 onRender。
const hasChildWork = includesSomeLane(
renderLanes,
workInProgress.childLanes,
);
if (hasChildWork) {
workInProgress.flags |= Update;
}

if (enableProfilerCommitHooks) {
// Schedule a passive effect for this Profiler to call onPostCommit hooks.
// This effect should be scheduled even if there is no onPostCommit callback for this Profiler,
// because the effect is also where times bubble to parent Profilers.
//
// 为此分析器调度一个被动效果以调用 onPostCommit 钩子。
// 即使此分析器没有 onPostCommit 回调,也应调度此效果,
// 因为在此效果中时间也会冒泡到父分析器。
workInProgress.flags |= Passive;
// Reset effect durations for the next eventual effect phase.
// These are reset during render to allow the DevTools commit hook a chance to read them,
// 为下一个可能的效果阶段重置效果持续时间。
// 在渲染期间会重置这些,以便 DevTools 提交钩子有机会读取它们,
const stateNode = workInProgress.stateNode;
stateNode.effectDuration = -0;
stateNode.passiveEffectDuration = -0;
}
}
break;
case ActivityComponent: {
const state: ActivityState | null = workInProgress.memoizedState;
if (state !== null) {
// We're dehydrated so we're not going to render the children. This is just
// to maintain push/pop symmetry.
// We know that this component will suspend again because if it has
// been unsuspended it has committed as a hydrated Activity component.
// If it needs to be retried, it should have work scheduled on it.
//
// 我们已经脱水,所以不会渲染子组件。这只是为了保持 push/pop 对称。
// 我们知道这个组件会再次挂起,因为如果它已经恢复挂起状态,它已经作为一个已水合的 Activity 组件提交了。
// 如果需要重试,它应该已经安排了相关工作。
workInProgress.flags |= DidCapture;
pushDehydratedActivitySuspenseHandler(workInProgress);
return null;
}
break;
}
case SuspenseComponent: {
const state: SuspenseState | null = workInProgress.memoizedState;
if (state !== null) {
if (state.dehydrated !== null) {
// We're not going to render the children, so this is just to maintain
// push/pop symmetry
//
// 我们不会渲染子元素,所以这只是为了保持 push/pop 的对称性
pushPrimaryTreeSuspenseHandler(workInProgress);
// We know that this component will suspend again because if it has
// been unsuspended it has committed as a resolved Suspense component.
// If it needs to be retried, it should have work scheduled on it.
//
// 我们知道这个组件会再次挂起,因为如果它已经取消挂起,它就已经作为一个
// 已解决的 Suspense 组件提交了。
// 如果它需要重试,应该已经安排了相应的工作。
workInProgress.flags |= DidCapture;
// We should never render the children of a dehydrated boundary until we
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
//
// 我们绝不应该渲染脱水边界的子节点,除非我们升级它。
// 我们返回 null 而不是 bailoutOnAlreadyFinishedWork。
return null;
}

// If this boundary is currently timed out, we need to decide
// whether to retry the primary children, or to skip over it and
// go straight to the fallback. Check the priority of the primary
// child fragment.
//
// 如果这个边界当前已超时,我们需要决定
// 是否重试主要的子组件,或者跳过它直接
// 转到备用。检查主要子组件片段的优先级。
const primaryChildFragment: Fiber = workInProgress.child as any;
const primaryChildLanes = primaryChildFragment.childLanes;
if (includesSomeLane(renderLanes, primaryChildLanes)) {
// The primary children have pending work. Use the normal path
// to attempt to render the primary children again.
//
// 主孩子有未完成的工作。使用常规路径
// 尝试再次渲染主孩子。
return updateSuspenseComponent(current, workInProgress, renderLanes);
} else {
// The primary child fragment does not have pending work marked
// on it
//
// 主子片段上没有标记待处理的工作
pushPrimaryTreeSuspenseHandler(workInProgress);
// The primary children do not have pending work with sufficient
// priority. Bailout.
//
// 主要子节点没有具有足够优先级的待处理工作。中止。
const child = bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
if (child !== null) {
// The fallback children have pending work. Skip over the
// primary children and work on the fallback.
//
// 后备子节点有待处理的工作。跳过主要子节点,处理后备子节点。
return child.sibling;
} else {
// Note: We can return `null` here because we already checked
// whether there were nested context consumers, via the call to
// `bailoutOnAlreadyFinishedWork` above.
//
// 注意:我们可以在这里返回 `null`,因为我们已经通过上面的
// 调用 `bailoutOnAlreadyFinishedWork` 检查了是否存在嵌套的上下文消费者。
return null;
}
}
} else {
pushPrimaryTreeSuspenseHandler(workInProgress);
}
break;
}
case SuspenseListComponent: {
if (workInProgress.flags & DidCapture) {
// Second pass caught.
// 第二次捕获。
return updateSuspenseListComponent(
current,
workInProgress,
renderLanes,
);
}
const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;

let hasChildWork = includesSomeLane(
renderLanes,
workInProgress.childLanes,
);

if (!hasChildWork) {
// Context changes may not have been propagated yet. We need to do
// that now, before we can decide whether to bail out.
// TODO: We use `childLanes` as a heuristic for whether there is
// remaining work in a few places, including
// `bailoutOnAlreadyFinishedWork` and
// `updateDehydratedSuspenseComponent`. We should maybe extract this
// into a dedicated function.
//
// 上下文的更改可能还没有传播。我们现在需要做这个,才能决定是否中止。
// TODO:我们在几个地方使用 `childLanes` 作为是否还有剩余工作的一种启发式方法,
// 包括 `bailoutOnAlreadyFinishedWork` 和 `updateDehydratedSuspenseComponent`。
// 我们可能应该把它提取成一个专用函数。
lazilyPropagateParentContextChanges(
current,
workInProgress,
renderLanes,
);
hasChildWork = includesSomeLane(renderLanes, workInProgress.childLanes);
}

if (didSuspendBefore) {
if (hasChildWork) {
// If something was in fallback state last time, and we have all the
// same children then we're still in progressive loading state.
// Something might get unblocked by state updates or retries in the
// tree which will affect the tail. So we need to use the normal
// path to compute the correct tail.
//
// 如果上次某个东西处于回退状态,并且我们拥有相同的所有子元素,那么我们仍然处于渐进加载状态。
// 树中的状态更新或重试可能会解除某些阻塞,从而影响尾部。因此,我们需要使用正常
// 路径来计算正确的尾部。
return updateSuspenseListComponent(
current,
workInProgress,
renderLanes,
);
}
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
//
// 如果所有子节点都没有工作,那就意味着没有任何一个节点被重试过,因此它们仍然
// 会像之前一样被阻塞。我们可以快速退出。
workInProgress.flags |= DidCapture;
}

// If nothing suspended before and we're rendering the same children,
// then the tail doesn't matter. Anything new that suspends will work
// in the "together" mode, so we can continue from the state we had.
//
// 如果之前没有任何挂起,而且我们正在渲染相同的子元素,
// 那么尾部无关紧要。任何新的挂起都会在“同时”模式下生效,
// 因此我们可以从已有的状态继续。
const renderState = workInProgress.memoizedState;
if (renderState !== null) {
// Reset to the "together" mode in case we've started a different
// update in the past but didn't complete it.
//
// 如果我们之前启动了不同的更新但未完成,则重置为“共同”模式。
renderState.rendering = null;
renderState.tail = null;
renderState.lastEffect = null;
}
pushSuspenseListContext(workInProgress, suspenseStackCursor.current);

if (hasChildWork) {
break;
} else {
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
//
// 如果所有子节点都没有任何工作,这意味着它们都没有被重试,所以它们仍然会像
// 以前一样被阻塞。我们可以快速退出。
return null;
}
}
case OffscreenComponent: {
// Need to check if the tree still needs to be deferred. This is
// almost identical to the logic used in the normal update path,
// so we'll just enter that. The only difference is we'll bail out
// at the next level instead of this one, because the child props
// have not changed. Which is fine.
// TODO: Probably should refactor `beginWork` to split the bailout
// path from the normal path. I'm tempted to do a labeled break here
// but I won't :)
//
// 需要检查树是否仍然需要延迟处理。
// 这几乎与普通更新路径中使用的逻辑相同,
// 所以我们直接进入即可。唯一的区别是我们会在下一层而不是这一层退出,
// 因为子属性没有改变。这没问题。
// TODO: 可能应该重构 `beginWork`,将中止路径与普通路径分开。
// 我很想在这里使用带标记的 break,但我不会 :)
workInProgress.lanes = NoLanes;
return updateOffscreenComponent(
current,
workInProgress,
renderLanes,
workInProgress.pendingProps,
);
}
case CacheComponent: {
const cache: Cache = current.memoizedState.cache;
pushCacheProvider(workInProgress, cache);
break;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
const instance: TracingMarkerInstance | null = workInProgress.stateNode;
if (instance !== null) {
pushMarkerInstance(workInProgress, instance);
}
break;
}
// Fallthrough
// 贯穿
}
case LegacyHiddenComponent: {
if (enableLegacyHidden) {
workInProgress.lanes = NoLanes;
return updateLegacyHiddenComponent(
current,
workInProgress,
renderLanes,
);
}
// Fallthrough
// 贯穿
}
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}