React Fiber 提交工作
一、作用
二、导出的变量
1. 在活动实例失焦后是否触发
备注
源码中的 311 行
export let shouldFireAfterActiveInstanceBlur: boolean = false;
三、在变更副作用前提交
备注
prepareForCommit()由宿主环境提供includesOnlyViewTransitionEligibleLanes()由 ReactFiberLane#includesOnlyViewTransitionEligibleLanes 实现resetAppearingViewTransitions()由 ReactFiberCommitViewTransitions#resetAppearingViewTransitions 实现
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes,
): void {
focusedInstanceHandle = prepareForCommit(root.containerInfo);
shouldFireAfterActiveInstanceBlur = false;
const isViewTransitionEligible =
enableViewTransition &&
includesOnlyViewTransitionEligibleLanes(committedLanes);
nextEffect = firstChild;
commitBeforeMutationEffects_begin(isViewTransitionEligible);
// We no longer need to track the active instance fiber
// 我们不再需要跟踪活动实例的 fiber
focusedInstanceHandle = null;
// We've found any matched pairs and can now reset.
// 我们已经找到所有匹配的配对,现在可以重置了。
resetAppearingViewTransitions();
}
四、提交突变效果
备注
resetComponentEffectTimers()由 ReactProfilerTimer#resetComponentEffectTimers 实现
export function commitMutationEffects(
root: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
) {
inProgressLanes = committedLanes;
inProgressRoot = root;
rootViewTransitionAffected = false;
inUpdateViewTransition = false;
resetComponentEffectTimers();
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
inProgressLanes = null;
inProgressRoot = null;
}
五、提交布局效果
备注
resetComponentEffectTimers()由 ReactProfilerTimer#resetComponentEffectTimers 实现
export function commitLayoutEffects(
finishedWork: Fiber,
root: FiberRoot,
committedLanes: Lanes,
): void {
inProgressLanes = committedLanes;
inProgressRoot = root;
resetComponentEffectTimers();
const current = finishedWork.alternate;
commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);
inProgressLanes = null;
inProgressRoot = null;
}
六、隐没布局效果
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现commitHookLayoutUnmountEffects()由 ReactFiberCommitEffects#commitHookLayoutUnmountEffects 实现safelyDetachRef()由 ReactFiberCommitEffects#safelyDetachRef 实现safelyCallComponentWillUnmount()由 ReactFiberCommitEffects#safelyCallComponentWillUnmount 实现commitHostSingletonRelease()由 ReactFiberCommitHostEffects#commitHostSingletonRelease 实现commitFragmentInstanceDeletionEffects()由 ReactFiberCommitHostEffects#commitFragmentInstanceDeletionEffects 实现untrackNamedViewTransition()由 ReactFiberDuplicateViewTransitions#trackNamedViewTransition 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现
export function disappearLayoutEffects(finishedWork: Fiber) {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// TODO (Offscreen) Check: flags & LayoutStatic
// 待办(屏幕外)检查:flags 和 LayoutStatic
commitHookLayoutUnmountEffects(
finishedWork,
finishedWork.return,
HookLayout,
);
recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}
case ClassComponent: {
// TODO (Offscreen) Check: flags & RefStatic
// 待办(屏幕外)检查:flags 和 RefStatic
safelyDetachRef(finishedWork, finishedWork.return);
const instance = finishedWork.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(
finishedWork,
finishedWork.return,
instance,
);
}
recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}
case HostSingleton: {
if (supportsSingletons) {
// TODO (Offscreen) Check: flags & RefStatic
// 待办(屏幕外)检查:flags 和 RefStatic
commitHostSingletonRelease(finishedWork);
}
// Expected fallthrough to HostComponent
// 预期会继续执行到 HostComponent
}
case HostHoistable:
case HostComponent: {
// TODO (Offscreen) Check: flags & RefStatic
// 待办(屏幕外)检查:flags 和 RefStatic
safelyDetachRef(finishedWork, finishedWork.return);
if (enableFragmentRefs && finishedWork.tag === HostComponent) {
commitFragmentInstanceDeletionEffects(finishedWork);
}
recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}
case OffscreenComponent: {
const isHidden = finishedWork.memoizedState !== null;
if (isHidden) {
// Nested Offscreen tree is already hidden. Don't disappear
// its effects.
// 嵌套的离屏树已经隐藏。不要让它的效果消失。
} else {
recursivelyTraverseDisappearLayoutEffects(finishedWork);
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
if (__DEV__) {
if (finishedWork.flags & ViewTransitionNamedStatic) {
untrackNamedViewTransition(finishedWork);
}
}
safelyDetachRef(finishedWork, finishedWork.return);
}
recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}
case Fragment: {
if (enableFragmentRefs) {
safelyDetachRef(finishedWork, finishedWork.return);
}
// Fallthrough
// 穿透
}
default: {
recursivelyTraverseDisappearLayoutEffects(finishedWork);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
(componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectErrors(prevEffectErrors);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
}
七、重新显示布局效果
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现commitHookLayoutEffects()由 ReactFiberCommitEffects#commitHookLayoutEffects 实现commitClassDidMount()由 ReactFiberCommitEffects#commitClassDidMount 实现commitClassHiddenCallbacks()由 ReactFiberCommitEffects#commitClassHiddenCallbacks 实现commitClassCallbacks()由 ReactFiberCommitEffects#commitClassCallbacks 实现safelyAttachRef()由 ReactFiberCommitEffects#safelyAttachRef 实现commitHostSingletonAcquisition()由 ReactFiberCommitHostEffects#commitHostSingletonAcquisition 实现commitFragmentInstanceInsertionEffects()由 ReactFiberCommitHostEffects#commitFragmentInstanceInsertionEffects 实现commitHostMount()由 ReactFiberCommitHostEffects#commitHostMount 实现pushNestedEffectDurations()由 ReactProfilerTimer#pushNestedEffectDurations 实现bubbleNestedEffectDurations()由 ReactProfilerTimer#bubbleNestedEffectDurations 实现commitProfilerUpdate()由 ReactFiberCommitEffects#commitProfilerUpdate 实现trackNamedViewTransition()由 ReactFiberDuplicateViewTransitions#trackNamedViewTransition 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现
export function reappearLayoutEffects(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
// This function visits both newly finished work and nodes that were re-used
// from a previously committed tree. We cannot check non-static flags if the
// node was reused.
// 这个函数会访问新完成的工作和以前已提交树中重用的节点。
// 如果节点被重用,我们无法检查非静态标志。
includeWorkInProgressEffects: boolean,
) {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
// Turn on layout effects in a tree that previously disappeared.
// 在之前消失的树中启用布局效果。
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
// TODO: Check flags & LayoutStatic
// 待办:检查标志和 LayoutStatic
commitHookLayoutEffects(finishedWork, HookLayout);
break;
}
case ClassComponent: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
commitClassDidMount(finishedWork);
commitClassHiddenCallbacks(finishedWork);
// If this is newly finished work, check for setState callbacks
// 如果这是新完成的工作,请检查 setState 回调
if (includeWorkInProgressEffects && flags & Callback) {
commitClassCallbacks(finishedWork);
}
// TODO: Check flags & RefStatic
// 待办:检查标志和 RefStatic
safelyAttachRef(finishedWork, finishedWork.return);
break;
}
// Unlike commitLayoutEffectsOnFiber, we don't need to handle HostRoot
// because this function only visits nodes that are inside an
// Offscreen fiber.
//
// 与 commitLayoutEffectsOnFiber 不同,我们不需要处理 HostRoot
// 因为此函数只访问位于 Offscreen fiber 内的节点。
//
// case HostRoot: {
// ...
// }
case HostSingleton: {
if (supportsSingletons) {
// We acquire the singleton instance first so it has appropriate
// styles before other layout effects run. This isn't perfect because
// an early sibling of the singleton may have an effect that can
// observe the singleton before it is acquired.
// @TODO move this to the mutation phase. The reason it isn't there yet
// is it seemingly requires an extra traversal because we need to move the
// disappear effect into a phase before the appear phase
//
// 我们首先获取单例实例,以便在其他布局效果运行之前,它具有适当的样式。这并不完美,因为
// 单例的早期兄弟节点可能有一个效果,可以在它被获取之前观察到单例。
// @TODO 将此移动到变更阶段。它之所以尚未移动,是因为似乎需要额外的遍历,因为我们需要
// 将消失效果移动到出现阶段之前的一个阶段
commitHostSingletonAcquisition(finishedWork);
// We fall through to the HostComponent case below.
// 我们会继续执行到下面的 HostComponent 情况。
}
// Fallthrough
}
case HostHoistable:
case HostComponent: {
// TODO: Enable HostText for RN
// 待办:为 RN 启用 HostText
if (enableFragmentRefs && finishedWork.tag === HostComponent) {
commitFragmentInstanceInsertionEffects(finishedWork);
}
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
//
// 渲染器可能会在宿主组件挂载后安排要执行的工作(例如,DOM 渲染器可能会为输入框和表单控件安
// 排自动聚焦)。
// 这些副作用应仅在组件首次挂载时提交,也就是在没有 current/alternate 时。
if (includeWorkInProgressEffects && current === null && flags & Update) {
commitHostMount(finishedWork);
}
// TODO: Check flags & Ref
// 待办:检查标志和引用
safelyAttachRef(finishedWork, finishedWork.return);
break;
}
case Profiler: {
// TODO: Figure out how Profiler updates should work with Offscreen
// 待办:弄清楚 Profiler 在离屏情况下的更新方式应该如何工作
if (includeWorkInProgressEffects && flags & Update) {
const prevProfilerEffectDuration = pushNestedEffectDurations();
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
const profilerInstance = finishedWork.stateNode;
if (enableProfilerTimer && enableProfilerCommitHooks) {
// Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a chance
// to read them first.
//
// 将布局效果持续时间传播到下一个最近的 Profiler 上层节点。
// 在下一次渲染之前不要重置这些值,以便 DevTools 有机会先读取它们。
profilerInstance.effectDuration += bubbleNestedEffectDurations(
prevProfilerEffectDuration,
);
}
commitProfilerUpdate(
finishedWork,
current,
commitStartTime,
profilerInstance.effectDuration,
);
} else {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
}
break;
}
case ActivityComponent: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
if (includeWorkInProgressEffects && flags & Update) {
// TODO: Delete this feature.
// 待办:删除此功能。
commitActivityHydrationCallbacks(finishedRoot, finishedWork);
}
break;
}
case SuspenseComponent: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
if (includeWorkInProgressEffects && flags & Update) {
// TODO: Delete this feature.
// 待办:删除此功能。
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
}
break;
}
case OffscreenComponent: {
const offscreenState: OffscreenState = finishedWork.memoizedState;
const isHidden = offscreenState !== null;
if (isHidden) {
// Nested Offscreen tree is still hidden. Don't re-appear its effects.
// 嵌套的离屏树仍然是隐藏的。不要重新显示其效果。
} else {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
}
// TODO: Check flags & Ref
// 待办:检查标志和引用
safelyAttachRef(finishedWork, finishedWork.return);
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
if (__DEV__) {
if (flags & ViewTransitionNamedStatic) {
trackNamedViewTransition(finishedWork);
}
}
safelyAttachRef(finishedWork, finishedWork.return);
break;
}
break;
}
case Fragment: {
if (enableFragmentRefs) {
safelyAttachRef(finishedWork, finishedWork.return);
}
// Fallthrough
// 穿透
}
default: {
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
(componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectErrors(prevEffectErrors);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
}
八、提交被动挂载效果
备注
resetComponentEffectTimers()由 ReactProfilerTimer#resetComponentEffectTimers 实现
export function commitPassiveMountEffects(
root: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
// 仅供性能分析用
renderEndTime: number, // Profiling-only
): void {
resetComponentEffectTimers();
commitPassiveMountOnFiber(
root,
finishedWork,
committedLanes,
committedTransitions,
enableProfilerTimer && enableComponentPerformanceTrack ? renderEndTime : 0,
);
}
九、重新连接被动效果
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现pushDeepEquality()由 ReactFiberPerformanceTrack#pushDeepEquality 实现logComponentRender()由 ReactFiberPerformanceTrack#logComponentRender 实现commitHookPassiveMountEffects()由 ReactFiberCommitEffects#commitHookPassiveMountEffects 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现popDeepEquality()由 ReactFiberPerformanceTrack#popDeepEquality 实现
export function reconnectPassiveEffects(
finishedRoot: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
// This function visits both newly finished work and nodes that were re-used
// from a previously committed tree. We cannot check non-static flags if the
// node was reused.
//
// 这个函数会访问新完成的工作和以前已提交树中重用的节点。
// 如果节点被重用,我们无法检查非静态标志。
includeWorkInProgressEffects: boolean,
// 仅用于性能分析。下一个 Fiber 或根节点完成的开始时间。
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
) {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
const prevDeepEquality = pushDeepEquality();
// If this component rendered in Profiling mode (DEV or in Profiler component) then log its
// render time. We do this after the fact in the passive effect to avoid the overhead of this
// getting in the way of the render characteristics and avoid the overhead of unwinding
// uncommitted renders.
//
// 如果这个组件在性能分析模式下渲染(开发模式或在 Profiler 组件中),则记录它的渲染时间。我们在被动
// effect 中完成此操作是为了避免该操作影响渲染特性,并避免在回滚未提交渲染时产生额外开销。
if (
enableProfilerTimer &&
enableComponentPerformanceTrack &&
includeWorkInProgressEffects &&
(finishedWork.mode & ProfileMode) !== NoMode &&
(finishedWork.actualStartTime as any as number) > 0 &&
(finishedWork.flags & PerformedWork) !== NoFlags
) {
logComponentRender(
finishedWork,
finishedWork.actualStartTime as any as number,
endTime,
inHydratedSubtree,
committedLanes,
);
}
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
// TODO: Check for PassiveStatic flag
// 待办:检查 PassiveStatic 标志
commitHookPassiveMountEffects(finishedWork, HookPassive);
break;
}
// Unlike commitPassiveMountOnFiber, we don't need to handle HostRoot
// because this function only visits nodes that are inside an
// Offscreen fiber.
//
// 与 commitPassiveMountOnFiber 不同,我们不需要处理 HostRoot
// 因为此函数只访问位于 Offscreen fiber 内的节点。
//
// case HostRoot: {
// ...
// }
case LegacyHiddenComponent: {
if (enableLegacyHidden) {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
if (includeWorkInProgressEffects && flags & Passive) {
// TODO: Pass `current` as argument to this function
// 待办:将 `current` 作为参数传递给此函数
const current: Fiber | null = finishedWork.alternate;
const instance: OffscreenInstance = finishedWork.stateNode;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}
}
break;
}
case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
const nextState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = nextState !== null;
if (isHidden) {
if (instance._visibility & OffscreenPassiveEffectsConnected) {
// The effects are currently connected. Update them.
// 当前效果已连接。请更新它们。
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
} else {
if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
// The effects are currently disconnected. Since the tree is hidden,
// don't connect them. This also applies to the initial render.
// "Atomic" effects are ones that need to fire on every commit,
// even during pre-rendering. An example is updating the reference
// count on cache instances.
//
// 这些效果目前是断开连接的。由于树是隐藏的,
// 不要连接它们。这也适用于初始渲染。
// “原子(Atomic)”效果是指需要在每次提交时触发的效果,
// 即使在预渲染期间也是如此。例如,更新缓存实例的引用计数。
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
} else {
// Legacy Mode: Fire the effects even if the tree is hidden.
// 旧版模式:即使树隐藏也触发效果。
instance._visibility |= OffscreenPassiveEffectsConnected;
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
}
}
} else {
// Tree is visible
// 树是可见的
// Since we're already inside a reconnecting tree, it doesn't matter
// whether the effects are currently connected. In either case, we'll
// continue traversing the tree and firing all the effects.
//
// 既然我们已经在一个重新连接的树中,无论效果当前是否已连接都无关紧要。无论哪种
// 情况,我们都会继续遍历树并触发所有效果。
//
// We do need to set the "connected" flag on the instance, though.
// 不过我们确实需要在实例上设置“已连接”标志。
instance._visibility |= OffscreenPassiveEffectsConnected;
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
}
if (includeWorkInProgressEffects && flags & Passive) {
// TODO: Pass `current` as argument to this function
// 待办:将 `current` 作为参数传递给此函数
const current: Fiber | null = finishedWork.alternate;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}
break;
}
case CacheComponent: {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
if (includeWorkInProgressEffects && flags & Passive) {
// TODO: Pass `current` as argument to this function
// TODO :将 `current` 作为参数传递给此函数
const current = finishedWork.alternate;
commitCachePassiveMountEffect(current, finishedWork);
}
break;
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
if (includeWorkInProgressEffects && flags & Passive) {
commitTracingMarkerPassiveMountEffect(finishedWork);
}
break;
}
// Intentional fallthrough to next branch
// 有意贯穿到下一个分支
}
default: {
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
(componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectErrors(prevEffectErrors);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
popDeepEquality(prevDeepEquality);
}
十、提交被动卸载效果
备注
resetComponentEffectTimers()由 ReactProfilerTimer#resetComponentEffectTimers 实现
export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
resetComponentEffectTimers();
commitPassiveUnmountOnFiber(finishedWork);
}
十一、累积挂起提交
备注
resetAppearingViewTransitions()由 ReactProfilerTimer#resetComponentEffectTimers 实现
export function accumulateSuspenseyCommit(
finishedWork: Fiber,
committedLanes: Lanes,
suspendedState: SuspendedState,
): void {
resetAppearingViewTransitions();
accumulateSuspenseyCommitOnFiber(
finishedWork,
committedLanes,
suspendedState,
);
}
十二、断开被动效果
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现commitHookPassiveUnmountEffects()由 ReactFiberCommitEffects#commitHookPassiveUnmountEffects 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现
export function disconnectPassiveEffect(finishedWork: Fiber): void {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// TODO: Check PassiveStatic flag
// 待办事项:检查 PassiveStatic 标志
commitHookPassiveUnmountEffects(
finishedWork,
finishedWork.return,
HookPassive,
);
// When disconnecting passive effects, we fire the effects in the same
// order as during a deletiong: parent before child
//
// 在断开被动效果时,我们按照删除时相同的顺序触发效果:先父后子
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
break;
}
case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
if (instance._visibility & OffscreenPassiveEffectsConnected) {
instance._visibility &= ~OffscreenPassiveEffectsConnected;
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
} else {
// The effects are already disconnected.
// 效果已经断开。
}
break;
}
default: {
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
(componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
popComponentEffectErrors(prevEffectErrors);
}
十三、在开发环境中调用 LayoutEffect 挂载
备注
commitHookEffectListMount()由 ReactFiberCommitEffects#commitHookEffectListMount 实现commitClassDidMount()由 ReactFiberCommitEffects#commitClassDidMount 实现
export function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
if (__DEV__) {
// We don't need to re-check StrictEffectsMode here.
// This function is only called if that check has already passed.
//
// 在这里我们不需要重新检查 StrictEffectsMode。
// 只有在该检查已经通过后,这个函数才会被调用。
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectListMount(HookLayout | HookHasEffect, fiber);
break;
}
case ClassComponent: {
commitClassDidMount(fiber);
break;
}
}
}
}
十四、在开发环境中调用被动效果挂载
备注
commitHookEffectListMount()由 ReactFiberCommitEffects#commitHookEffectListMount 实现
export function invokePassiveEffectMountInDEV(fiber: Fiber): void {
if (__DEV__) {
// We don't need to re-check StrictEffectsMode here.
// This function is only called if that check has already passed.
//
// 在这里我们不需要重新检查 StrictEffectsMode。
// 只有在该检查已经通过后,这个函数才会被调用。
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectListMount(HookPassive | HookHasEffect, fiber);
break;
}
}
}
}
十五、在开发环境中调用 LayoutEffect 卸载
备注
commitHookEffectListUnmount()由 ReactFiberCommitEffects#commitHookEffectListUnmount 实现safelyCallComponentWillUnmount()由 ReactFiberCommitEffects#safelyCallComponentWillUnmount 实现
export function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
if (__DEV__) {
// We don't need to re-check StrictEffectsMode here.
// This function is only called if that check has already passed.
//
// 在这里我们不需要重新检查 StrictEffectsMode。
// 只有在该检查已经通过后,这个函数才会被调用。
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
fiber,
fiber.return,
);
break;
}
case ClassComponent: {
const instance = fiber.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(fiber, fiber.return, instance);
}
break;
}
}
}
}
十六、在开发环境中调用被动效果卸载
备注
commitHookEffectListUnmount()由 ReactFiberCommitEffects#commitHookEffectListUnmount 实现
export function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
if (__DEV__) {
// We don't need to re-check StrictEffectsMode here.
// This function is only called if that check has already passed.
//
// 在这里我们不需要重新检查 StrictEffectsMode。
// 只有在该检查已经通过后,这个函数才会被调用。
switch (fiber.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookEffectListUnmount(
HookPassive | HookHasEffect,
fiber,
fiber.return,
);
}
}
}
}
十七、常量
备注
源码中的 302 行
// 可能弱集合
const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
十八、变量
1. 离屏子树已隐藏
备注
源码中的 291 - 300 、 304 - 310 、 313 - 318 行
// Used during the commit phase to track the state of the Offscreen component stack.
// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
//
// 在提交阶段使用,用于跟踪 Offscreen 组件栈的状态。
// 让我们可以避免遍历返回路径来查找最近的 Offscreen 祖先。
// + 离屏子树已隐藏
let offscreenSubtreeIsHidden: boolean = false;
// + 离屏子树已隐藏
let offscreenSubtreeWasHidden: boolean = false;
// Track whether there's a hidden offscreen above with no HostComponent between. If so,
// it overrides the hiddenness of the HostComponent below.
//
// 跟踪是否有上方隐藏的屏幕外元素且中间没有 HostComponent。如果有,它会覆盖下面 HostComponent 的隐藏状态。
// + 离屏直接父元素被隐藏
let offscreenDirectParentIsHidden: boolean = false;
// Used to track if a form needs to be reset at the end of the mutation phase.
// 用于跟踪表单是否需要在变异阶段结束时重置。
// + 需要重置表单
let needsFormReset = false;
// + 下一个效果
let nextEffect: Fiber | null = null;
// Used for Profiling builds to track updaters.
// 用于性能分析构建以跟踪更新器。
// + 进行中的车道
let inProgressLanes: Lanes | null = null;
// + 进行中根
let inProgressRoot: FiberRoot | null = null;
// + 焦点实例句柄
let focusedInstanceHandle: null | Fiber = null;
// Used during the commit phase to track whether a parent ViewTransition component
// might have been affected by any mutations / relayouts below.
//
// 在提交阶段使用,用于跟踪父 ViewTransition 组件是否可能受到下方任何变更/重新布局的影响。
// + 视图过渡上下文已更改
let viewTransitionContextChanged: boolean = false;
// + 在更新视图过渡中
let inUpdateViewTransition: boolean = false;
// + 根视图过渡受影响
let rootViewTransitionAffected: boolean = false;
// + 根视图过渡名称已取消
let rootViewTransitionNameCanceled: boolean = false;
2. 宿主环境父级
// These are tracked on the stack as we recursively traverse a
// deleted subtree.
// TODO: Update these during the whole mutation phase, not just during
// a deletion.
//
// 当我们递归遍历被删除的子树时,这些会在栈上跟踪。
// TODO:在整个变更阶段更新这些,而不仅仅是在删除期间。
// 宿主环境父级
let hostParent: Instance | Container | null = null;
// 宿主环境父级是否是容器
let hostParentIsContainer: boolean = false;
3. 当前可提升根
let currentHoistableRoot: HoistableRoot | null = null;
4. 已水合子树
备注
源码中的 3505 行
let inHydratedSubtree = false;
5. 悬而未决提交标志
// If we're inside a brand new tree, or a tree that was already visible, then we
// should only suspend host components that have a ShouldSuspendCommit flag.
// Components without it haven't changed since the last commit, so we can skip
// over those.
// 如果我们在一棵全新的树中,或者在一棵已经可见的树中,那么我们应该只暂停那些具有
// ShouldSuspendCommit 标记的宿主组件。没有该标记的组件自上次提交以来没有发生变化,所以我们
// 可以跳过它们。
//
// When we enter a tree that is being revealed (going from hidden -> visible),
// we need to suspend _any_ component that _may_ suspend. Even if they're
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
//
// 当我们进入一个正在被显示的树(从隐藏 -> 可见)时,我们需要暂停任何可能会挂起的组件。即使它们
// 已经在“当前”树中。因为它们的可见性发生了变化,浏览器可能还没有预渲染它们。因此,我们检查
// MaySuspendCommit 标志。
//
// Note that MaySuspendCommit and ShouldSuspendCommit also includes named
// ViewTransitions so that we know to also visit those to collect appearing
// pairs.
//
// 请注意,MaySuspendCommit 和 ShouldSuspendCommit 也包括命名的 ViewTransitions,这样
// 我们就知道还要访问它们以收集出现的对。
// + 悬而未决提交标志
let suspenseyCommitFlag: Flags = ShouldSuspendCommit;
十九、工具
1. 判定是否为水合元素的父级
function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {
if (finishedWork.tag === ActivityComponent) {
const prevState: ActivityState | null = current.memoizedState;
const nextState: ActivityState | null = finishedWork.memoizedState;
return prevState !== null && nextState === null;
} else if (finishedWork.tag === SuspenseComponent) {
const prevState: SuspenseState | null = current.memoizedState;
const nextState: SuspenseState | null = finishedWork.memoizedState;
return (
prevState !== null &&
prevState.dehydrated !== null &&
(nextState === null || nextState.dehydrated === null)
);
} else if (finishedWork.tag === HostRoot) {
return (
(current.memoizedState as RootState).isDehydrated &&
(finishedWork.flags & ForceClientRender) === NoFlags
);
} else {
return false;
}
}
2. 提交前变更效果(开始)
备注
trackEnterViewTransitions()由 ReactFiberCommitViewTransitions#trackEnterViewTransitions 实现commitExitViewTransitions()由 ReactFiberCommitViewTransitions#commitExitViewTransitions 实现commitNestedViewTransitions()由 ReactFiberCommitViewTransitions#commitNestedViewTransitions 实现
function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {
// If this commit is eligible for a View Transition we look into all mutated subtrees.
// TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.
//
// 如果此提交符合视图过渡的条件,我们会检查所有被修改的子树。
// 待办:我们可以在渲染阶段通过在快照子树上标记来进行优化。
const subtreeMask = isViewTransitionEligible
? BeforeAndAfterMutationTransitionMask
: BeforeMutationMask;
while (nextEffect !== null) {
const fiber = nextEffect;
// This phase is only used for beforeActiveInstanceBlur.
// Let's skip the whole loop if it's off.
//
// 这个阶段只在 beforeActiveInstanceBlur 之前使用。
// 如果关闭,就跳过整个循环。
if (enableCreateEventHandleAPI || isViewTransitionEligible) {
// TODO: Should wrap this in flags check, too, as optimization
// TODO: 也应该在标志检查中包裹这个,以进行优化
const deletions = fiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
commitBeforeMutationEffectsDeletion(
deletion,
isViewTransitionEligible,
);
}
}
}
if (
enableViewTransition &&
fiber.alternate === null &&
(fiber.flags & Placement) !== NoFlags
) {
// Skip before mutation effects of the children because we don't want
// to trigger updates of any nested view transitions and we shouldn't
// have any other before mutation effects since snapshot effects are
// only applied to updates. TODO: Model this using only flags.
//
// 跳过子元素的变更前效果,因为我们不希望触发任何嵌套视图过渡的更新,而且我们不应该有
// 其他变更前效果,因为快照效果只应用于更新。TODO:仅使用标志来建模这个过程。
if (isViewTransitionEligible) {
trackEnterViewTransitions(fiber);
}
commitBeforeMutationEffects_complete(isViewTransitionEligible);
continue;
}
// TODO: This should really unify with the switch in
// commitBeforeMutationEffectsOnFiber recursively.
//
// 待办:这实际上应该与 commitBeforeMutationEffectsOnFiber 中的 switch 递归统一。
if (enableViewTransition && fiber.tag === OffscreenComponent) {
const isModernRoot =
disableLegacyMode || (fiber.mode & ConcurrentMode) !== NoMode;
if (isModernRoot) {
const current = fiber.alternate;
const isHidden = fiber.memoizedState !== null;
if (isHidden) {
if (
current !== null &&
current.memoizedState === null &&
isViewTransitionEligible
) {
// Was previously mounted as visible but is now hidden.
// 之前是挂载为可见的,但现在已隐藏。
commitExitViewTransitions(current);
}
// Skip before mutation effects of the children because they're hidden.
// 在子元素的变异效果之前跳过,因为它们是隐藏的。
commitBeforeMutationEffects_complete(isViewTransitionEligible);
continue;
} else if (current !== null && current.memoizedState !== null) {
// Was previously mounted as hidden but is now visible.
// Skip before mutation effects of the children because we don't want
// to trigger updates of any nested view transitions and we shouldn't
// have any other before mutation effects since snapshot effects are
// only applied to updates. TODO: Model this using only flags.
//
// 之前被挂载为隐藏,但现在可见。
// 在子元素的变更前跳过,因为我们不想触发任何嵌套视图过渡的更新,
// 并且我们不应该有任何其他变更前的副作用,因为快照副作用只应用于更新。
// TODO: 仅使用标志来建模这个过程。
if (isViewTransitionEligible) {
trackEnterViewTransitions(fiber);
}
commitBeforeMutationEffects_complete(isViewTransitionEligible);
continue;
}
}
}
const child = fiber.child;
if ((fiber.subtreeFlags & subtreeMask) !== NoFlags && child !== null) {
child.return = fiber;
nextEffect = child;
} else {
if (isViewTransitionEligible) {
// We are inside an updated subtree. Any mutations that affected the
// parent HostInstance's layout or set of children (such as reorders)
// might have also affected the positioning or size of the inner
// ViewTransitions. Therefore we need to find them inside.
//
// 我们在一个更新的子树内部。任何影响父级 HostInstance 的布局或子节点集合的
// 变更(例如重新排序)也可能影响内部 ViewTransitions 的位置或大小。因此我们
// 需要在其中找到它们。
commitNestedViewTransitions(fiber);
}
commitBeforeMutationEffects_complete(isViewTransitionEligible);
}
}
}
3. 提交前变更效果(完成)
function commitBeforeMutationEffects_complete(
isViewTransitionEligible: boolean,
) {
while (nextEffect !== null) {
const fiber = nextEffect;
commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);
const sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
4. 在 Fiber 上提交变异效果之前
备注
doesFiberContain()由 ReactFiberTreeReflection#doesFiberContain 实现beforeActiveInstanceBlur()由 ReactFiberWorkLoop 提供commitClassSnapshot()由 ReactFiberCommitEffects#commitClassSnapshot 实现clearContainer()由 ReactFiberWorkLoop 提供commitBeforeUpdateViewTransition()由 ReactFiberCommitViewTransitions#commitBeforeUpdateViewTransition 实现
function commitBeforeMutationEffectsOnFiber(
finishedWork: Fiber,
isViewTransitionEligible: boolean,
) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if (enableCreateEventHandleAPI) {
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
// Check to see if the focused element was inside of a hidden (Suspense) subtree.
// TODO: Move this out of the hot path using a dedicated effect tag.
// TODO: This should consider Offscreen in general and not just SuspenseComponent.
//
// 检查焦点元素是否位于隐藏(Suspense)子树中。
// 待办:使用专用的 effect 标签将其移出关键路径。
// 待办:这应该考虑一般的 Offscreen,而不仅仅是 SuspenseComponent。
if (
finishedWork.tag === SuspenseComponent &&
isSuspenseBoundaryBeingHidden(current, finishedWork) &&
doesFiberContain(finishedWork, focusedInstanceHandle)
) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(finishedWork);
}
}
}
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
if (enableUseEffectEventHook) {
if ((flags & Update) !== NoFlags) {
const updateQueue: FunctionComponentUpdateQueue | null =
finishedWork.updateQueue as any;
const eventPayloads =
updateQueue !== null ? updateQueue.events : null;
if (eventPayloads !== null) {
for (let ii = 0; ii < eventPayloads.length; ii++) {
const { ref, nextImpl } = eventPayloads[ii];
ref.impl = nextImpl;
}
}
}
}
break;
}
case ClassComponent: {
if ((flags & Snapshot) !== NoFlags) {
if (current !== null) {
commitClassSnapshot(finishedWork, current);
}
}
break;
}
case HostRoot: {
if ((flags & Snapshot) !== NoFlags) {
if (supportsMutation) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
}
break;
}
case HostComponent:
case HostHoistable:
case HostSingleton:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
// 对这些组件类型无需操作
break;
case ViewTransitionComponent:
if (enableViewTransition) {
if (isViewTransitionEligible) {
if (current === null) {
// This is a new mount. We should have handled this as part of the
// Placement effect or it is deeper inside a entering transition.
//
// 这是一个新的挂载。我们应该在布局效果中处理它,或者它位于进入过渡的更深层。
} else {
// Something may have mutated within this subtree. This might need to cause
// a cross-fade of this parent. We first assign old names to the
// previous tree in the before mutation phase in case we need to.
// TODO: This walks the tree that we might continue walking anyway.
// We should just stash the parent ViewTransitionComponent and continue
// walking the tree until we find HostComponent but to do that we need
// to use a stack which requires refactoring this phase.
//
// 在这个子树中可能发生了某种变异。这可能需要导致父节点的交叉淡入淡出。我们首先在变异前阶段
// 将旧名称分配给之前的树,以防我们需要这样做。
// TODO: 这会遍历我们可能仍会继续遍历的树。我们应该只是暂存父节点的
// ViewTransitionComponent 并继续遍历树,直到我们找到 HostComponent,但要做到这一
// 点,我们需要使用一个堆栈,这需要重构这个阶段。
commitBeforeUpdateViewTransition(current, finishedWork);
}
}
break;
}
// Fallthrough
default: {
if ((flags & Snapshot) !== NoFlags) {
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
}
}
5. 提交前变异效果删除
备注
doesFiberContain()由 ReactFiberTreeReflection#doesFiberContain 实现beforeActiveInstanceBlur()由 ReactFiberWorkLoop 提供commitExitViewTransitions()由 ReactFiberCommitViewTransitions#commitExitViewTransitions 实现
function commitBeforeMutationEffectsDeletion(
deletion: Fiber,
isViewTransitionEligible: boolean,
) {
if (enableCreateEventHandleAPI) {
// TODO (effects) It would be nice to avoid calling doesFiberContain()
// Maybe we can repurpose one of the subtreeFlags positions for this instead?
// Use it to store which part of the tree the focused instance is in?
// This assumes we can safely determine that instance during the "render" phase.
//
// TODO(效果)最好能避免调用 doesFiberContain() 或许我们可以改用 subtreeFlags 的某
// 个位置来实现这个功能?
// 用它来存储焦点实例所在的树的部分?这假设我们可以在“渲染”阶段安全地确定该实例。
if (doesFiberContain(deletion, focusedInstanceHandle as any as Fiber)) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(deletion);
}
}
if (isViewTransitionEligible) {
commitExitViewTransitions(deletion);
}
}
6. 在 Fiber 上提交布局副作用
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现commitHookLayoutEffects()由 ReactFiberCommitEffects#commitHookLayoutEffects 实现commitClassLayoutLifecycles()由 ReactFiberCommitEffects#commitClassLayoutLifecycles 实现commitClassCallbacks()由 ReactFiberCommitEffects#commitClassCallbacks 实现safelyAttachRef()由 ReactFiberCommitEffects#safelyAttachRef 实现pushNestedEffectDurations()由 ReactProfilerTimer#pushNestedEffectDurations 实现commitRootCallbacks()由 ReactFiberCommitEffects#commitRootCallbacks 实现popNestedEffectDurations()由 ReactProfilerTimer#popNestedEffectDurations 实现commitHostSingletonAcquisition()由 ReactFiberCommitHostEffects#commitHostSingletonAcquisition 实现commitHostMount()由 ReactFiberCommitHostEffects#commitHostMount 实现commitHostHydratedInstance()由 ReactFiberCommitHostEffects#commitHostHydratedInstance 实现bubbleNestedEffectDurations()由 ReactProfilerTimer#bubbleNestedEffectDurations 实现commitProfilerUpdate()由 ReactFiberCommitEffects#commitProfilerUpdate 实现retryDehydratedSuspenseBoundary()由 ReactFiberWorkLoop 实现registerSuspenseInstanceRetry()由宿主环境提供logComponentReappeared()由 ReactFiberPerformanceTrack#logComponentReappeared 实现trackNamedViewTransition()由 ReactFiberDuplicateViewTransitions#trackNamedViewTransition 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现logComponentMount()由 ReactFiberPerformanceTrack#logComponentMount 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
// When updating this function, also update reappearLayoutEffects, which does
// most of the same things when an offscreen tree goes from hidden -> visible.
//
// 更新此函数时,也请更新 reappearLayoutEffects,当屏幕外树从
// 隐藏 -> 可见
// 时,它会执行大部分相同的操作。
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
if (flags & Update) {
commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
}
break;
}
case ClassComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
if (flags & Update) {
commitClassLayoutLifecycles(finishedWork, current);
}
if (flags & Callback) {
commitClassCallbacks(finishedWork);
}
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}
break;
}
case HostRoot: {
const prevProfilerEffectDuration = pushNestedEffectDurations();
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
if (flags & Callback) {
commitRootCallbacks(finishedWork);
}
if (enableProfilerTimer && enableProfilerCommitHooks) {
finishedRoot.effectDuration += popNestedEffectDurations(
prevProfilerEffectDuration,
);
}
break;
}
case HostSingleton: {
if (supportsSingletons) {
// We acquire the singleton instance first so it has appropriate
// styles before other layout effects run. This isn't perfect because
// an early sibling of the singleton may have an effect that can
// observe the singleton before it is acquired.
//
// 我们首先获取单例实例,以便在其他布局效果运行之前,它具有适当的样式。这并
// 不完美,因为单例的早期兄弟节点可能有一个效果,可以在它被获取之前观察到单例。
//
// @TODO move this to the mutation phase. The reason it isn't there yet
// is it seemingly requires an extra traversal because we need to move the
// disappear effect into a phase before the appear phase
//
// @TODO 将此移动到变更阶段。它之所以尚未移动,是因为似乎需要额外的遍历,因为我们
// 需要将消失效果移动到出现阶段之前的一个阶段
if (current === null && flags & Update) {
// Unlike in the reappear path we only acquire on new mount
// 与重新出现路径不同,我们只在新挂载时获取
commitHostSingletonAcquisition(finishedWork);
}
// We fall through to the HostComponent case below.
// 我们会继续执行到下面的 HostComponent 情况。
}
// Fallthrough
// 贯穿
}
case HostHoistable:
case HostComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
// Renderers may schedule work to be done after host components are mounted
// (eg DOM renderer may schedule auto-focus for inputs and form controls).
// These effects should only be committed when components are first mounted,
// aka when there is no current/alternate.
//
// 渲染器可能会在宿主组件挂载后安排要执行的工作(例如,DOM 渲染器可能会为输入框和表单控件
// 安排自动聚焦)。这些副作用应仅在组件首次挂载时提交,也就是在没有 current/alternate 时。
if (current === null) {
if (flags & Update) {
commitHostMount(finishedWork);
} else if (flags & Hydrate) {
commitHostHydratedInstance(finishedWork);
}
}
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}
break;
}
case Profiler: {
// TODO: Should this fire inside an offscreen tree? Or should it wait to
// fire when the tree becomes visible again.
//
// 待办:这应该在屏幕外的树中触发吗?还是应该等到树再次可见时再触发。
if (flags & Update) {
const prevProfilerEffectDuration = pushNestedEffectDurations();
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
const profilerInstance = finishedWork.stateNode;
if (enableProfilerTimer && enableProfilerCommitHooks) {
// Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a
// chance to read them first.
//
// 将布局效果持续时间传播到下一个最近的 Profiler 上层节点。
// 在下一次渲染之前不要重置这些值,以便 DevTools 有机会先读取它们。
profilerInstance.effectDuration += bubbleNestedEffectDurations(
prevProfilerEffectDuration,
);
}
commitProfilerUpdate(
finishedWork,
current,
commitStartTime,
profilerInstance.effectDuration,
);
} else {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
}
break;
}
case ActivityComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
if (flags & Update) {
commitActivityHydrationCallbacks(finishedRoot, finishedWork);
}
break;
}
case SuspenseComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
if (flags & Update) {
commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
}
if (flags & Callback) {
// This Boundary is in fallback and has a dehydrated Suspense instance.
// We could in theory assume the dehydrated state but we recheck it for
// certainty.
//
// 这个边界处于回退状态,并且有一个已脱水的 Suspense 实例。
// 理论上我们可以假设它的脱水状态,但我们会重新检查以确保准确。
const finishedState: SuspenseState | null = finishedWork.memoizedState;
if (finishedState !== null) {
const dehydrated = finishedState.dehydrated;
if (dehydrated !== null) {
// Register a callback to retry this boundary once the server has sent the result.
// 注册一个回调,以便在服务器发送结果后重试此边界。
const retry = retryDehydratedSuspenseBoundary.bind(
null,
finishedWork,
);
registerSuspenseInstanceRetry(dehydrated, retry);
}
}
}
break;
}
case OffscreenComponent: {
const isModernRoot =
disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;
if (isModernRoot) {
const isHidden = finishedWork.memoizedState !== null;
const newOffscreenSubtreeIsHidden =
isHidden || offscreenSubtreeIsHidden;
if (newOffscreenSubtreeIsHidden) {
// The Offscreen tree is hidden. Skip over its layout effects.
// 离屏树是隐藏的。跳过它的布局效果。
} else {
// The Offscreen tree is visible.
// 离屏树是可见的。
const wasHidden = current !== null && current.memoizedState !== null;
const newOffscreenSubtreeWasHidden =
wasHidden || offscreenSubtreeWasHidden;
const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
// This is the root of a reappearing boundary. As we continue
// traversing the layout effects, we must also re-mount layout
// effects that were unmounted when the Offscreen subtree was
// hidden. So this is a superset of the normal commitLayoutEffects.
//
// 这是一个重新出现边界的根节点。当我们继续
// 遍历布局效果时,我们还必须重新挂载那些在 Offscreen 子树
// 被隐藏时卸载的布局效果。所以这是普通 commitLayoutEffects 的超集。
const includeWorkInProgressEffects =
(finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
recursivelyTraverseReappearLayoutEffects(
finishedRoot,
finishedWork,
includeWorkInProgressEffects,
);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentReappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
} else {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
}
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
}
} else {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
if (__DEV__) {
if (flags & ViewTransitionNamedStatic) {
trackNamedViewTransition(finishedWork);
}
}
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}
break;
}
break;
}
case Fragment:
if (enableFragmentRefs) {
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}
}
// Fallthrough
// 贯穿
default: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0
) {
if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
if (
// Insertion
// 插入
finishedWork.alternate === null &&
finishedWork.return !== null &&
finishedWork.return.alternate !== null &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
const isHydration = isHydratingParent(
finishedWork.return.alternate,
finishedWork.return,
);
if (!isHydration) {
logComponentMount(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectErrors(prevEffectErrors);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
}
7. 中止根过渡
function abortRootTransitions(
root: FiberRoot,
abort: TransitionAbort,
deletedTransitions: Set<Transition>,
deletedOffscreenInstance: OffscreenInstance | null,
isInDeletedTree: boolean,
) {
if (enableTransitionTracing) {
const rootTransitions = root.incompleteTransitions;
deletedTransitions.forEach(transition => {
if (rootTransitions.has(transition)) {
const transitionInstance: TracingMarkerInstance = rootTransitions.get(
transition,
) as any;
if (transitionInstance.aborts === null) {
transitionInstance.aborts = [];
}
transitionInstance.aborts.push(abort);
if (deletedOffscreenInstance !== null) {
if (
transitionInstance.pendingBoundaries !== null &&
transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)
) {
transitionInstance.pendingBoundaries.delete(
deletedOffscreenInstance,
);
}
}
}
});
}
}
8. 中止跟踪标记转换
备注
addMarkerIncompleteCallbackToPendingTransition()由 ReactFiberWorkLoop 提供addMarkerProgressCallbackToPendingTransition()由 ReactFiberWorkLoop 提供
function abortTracingMarkerTransitions(
abortedFiber: Fiber,
abort: TransitionAbort,
deletedTransitions: Set<Transition>,
deletedOffscreenInstance: OffscreenInstance | null,
isInDeletedTree: boolean,
) {
if (enableTransitionTracing) {
const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;
const markerTransitions = markerInstance.transitions;
const pendingBoundaries = markerInstance.pendingBoundaries;
if (markerTransitions !== null) {
// TODO: Refactor this code. Is there a way to move this code to
// the deletions phase instead of calculating it here while making sure
// complete is called appropriately?
//
// 待办:重构这段代码。是否有办法把这段代码移动到删除阶段,而不是在这里计算,同时确保
// 正确调用 complete?
deletedTransitions.forEach(transition => {
// If one of the transitions on the tracing marker is a transition
// that was in an aborted subtree, we will abort that tracing marker
//
// 如果跟踪标记上的某个转换是来自已中止子树的转换,我们将中止该跟踪标记
if (
abortedFiber !== null &&
markerTransitions.has(transition) &&
(markerInstance.aborts === null ||
!markerInstance.aborts.includes(abort))
) {
if (markerInstance.transitions !== null) {
if (markerInstance.aborts === null) {
markerInstance.aborts = [abort];
addMarkerIncompleteCallbackToPendingTransition(
abortedFiber.memoizedProps.name,
markerInstance.transitions,
markerInstance.aborts,
);
} else {
markerInstance.aborts.push(abort);
}
// We only want to call onTransitionProgress when the marker
// hasn't been deleted
//
// 我们只希望在标记未被删除时调用 onTransitionProgress
if (
deletedOffscreenInstance !== null &&
!isInDeletedTree &&
pendingBoundaries !== null &&
pendingBoundaries.has(deletedOffscreenInstance)
) {
pendingBoundaries.delete(deletedOffscreenInstance);
addMarkerProgressCallbackToPendingTransition(
abortedFiber.memoizedProps.name,
deletedTransitions,
pendingBoundaries,
);
}
}
}
});
}
}
}
9. 中止已删除 Fiber 的父标记过渡
function abortParentMarkerTransitionsForDeletedFiber(
abortedFiber: Fiber,
abort: TransitionAbort,
deletedTransitions: Set<Transition>,
deletedOffscreenInstance: OffscreenInstance | null,
isInDeletedTree: boolean,
) {
if (enableTransitionTracing) {
// Find all pending markers that are waiting on child suspense boundaries in the
// aborted subtree and cancels them
//
// 查找在已中止子树中等待子级挂起边界的所有挂起标记,并取消它们
let fiber: null | Fiber = abortedFiber;
while (fiber !== null) {
switch (fiber.tag) {
case TracingMarkerComponent:
abortTracingMarkerTransitions(
fiber,
abort,
deletedTransitions,
deletedOffscreenInstance,
isInDeletedTree,
);
break;
case HostRoot:
const root = fiber.stateNode;
abortRootTransitions(
root,
abort,
deletedTransitions,
deletedOffscreenInstance,
isInDeletedTree,
);
break;
default:
break;
}
fiber = fiber.return;
}
}
}
10. 提交过渡进度
备注
addMarkerProgressCallbackToPendingTransition()由 ReactFiberWorkLoop 提供addTransitionProgressCallbackToPendingTransition()由 ReactFiberWorkLoop 提供addMarkerCompleteCallbackToPendingTransition()由 ReactFiberWorkLoop 提供addTransitionProgressCallbackToPendingTransition()由 ReactFiberWorkLoop 提供
function commitTransitionProgress(offscreenFiber: Fiber) {
if (enableTransitionTracing) {
// This function adds suspense boundaries to the root
// or tracing marker's pendingBoundaries map.
// When a suspense boundary goes from a resolved to a fallback
// state we add the boundary to the map, and when it goes from
// a fallback to a resolved state, we remove the boundary from
// the map.
//
// 这个函数将 suspense 边界添加到根节点或跟踪标记的 pendingBoundaries 映射中。
// 当一个 suspense 边界从已解析状态变为回退状态时,我们将该边界添加到映射中;
// 当它从回退状态变回已解析状态时,我们从映射中移除该边界。
// We use stateNode on the Offscreen component as a stable object
// that doesnt change from render to render. This way we can
// distinguish between different Offscreen instances (vs. the same
// Offscreen instance with different fibers)
//
// 我们在 Offscreen 组件上使用 stateNode 作为一个稳定的对象。它在每次渲染时
// 不会改变。这样我们可以区分不同的 Offscreen 实例(与具有不同 fiber 的同一个
// Offscreen 实例相对比)
const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;
let prevState: SuspenseState | null = null;
const previousFiber = offscreenFiber.alternate;
if (previousFiber !== null && previousFiber.memoizedState !== null) {
prevState = previousFiber.memoizedState;
}
const nextState: SuspenseState | null = offscreenFiber.memoizedState;
const wasHidden = prevState !== null;
const isHidden = nextState !== null;
const pendingMarkers = offscreenInstance._pendingMarkers;
// If there is a name on the suspense boundary, store that in
// the pending boundaries.
//
// 如果 suspense 边界上有名字,将其存储在待处理边界中。
let name = null;
const parent = offscreenFiber.return;
if (
parent !== null &&
parent.tag === SuspenseComponent &&
parent.memoizedProps.name
) {
name = parent.memoizedProps.name;
}
if (!wasHidden && isHidden) {
// The suspense boundaries was just hidden. Add the boundary
// to the pending boundary set if it's there
//
// 暂停边界刚刚被隐藏。如果边界存在,将其添加到待处理边界集合中
if (pendingMarkers !== null) {
pendingMarkers.forEach(markerInstance => {
const pendingBoundaries = markerInstance.pendingBoundaries;
const transitions = markerInstance.transitions;
const markerName = markerInstance.name;
if (
pendingBoundaries !== null &&
!pendingBoundaries.has(offscreenInstance)
) {
pendingBoundaries.set(offscreenInstance, {
name,
});
if (transitions !== null) {
if (
markerInstance.tag === TransitionTracingMarker &&
markerName !== null
) {
addMarkerProgressCallbackToPendingTransition(
markerName,
transitions,
pendingBoundaries,
);
} else if (markerInstance.tag === TransitionRoot) {
transitions.forEach(transition => {
addTransitionProgressCallbackToPendingTransition(
transition,
pendingBoundaries,
);
});
}
}
}
});
}
} else if (wasHidden && !isHidden) {
// The suspense boundary went from hidden to visible. Remove
// the boundary from the pending suspense boundaries set
// if it's there
//
// suspense 边界从隐藏变为可见。如果存在,从待处理的 suspense 边界集合中
// 删除该边界
if (pendingMarkers !== null) {
pendingMarkers.forEach(markerInstance => {
const pendingBoundaries = markerInstance.pendingBoundaries;
const transitions = markerInstance.transitions;
const markerName = markerInstance.name;
if (
pendingBoundaries !== null &&
pendingBoundaries.has(offscreenInstance)
) {
pendingBoundaries.delete(offscreenInstance);
if (transitions !== null) {
if (
markerInstance.tag === TransitionTracingMarker &&
markerName !== null
) {
addMarkerProgressCallbackToPendingTransition(
markerName,
transitions,
pendingBoundaries,
);
// If there are no more unresolved suspense boundaries, the interaction
// is considered finished
//
// 如果没有未解决的挂起边界,该交互被视为完成
if (pendingBoundaries.size === 0) {
if (markerInstance.aborts === null) {
addMarkerCompleteCallbackToPendingTransition(
markerName,
transitions,
);
}
markerInstance.transitions = null;
markerInstance.pendingBoundaries = null;
markerInstance.aborts = null;
}
} else if (markerInstance.tag === TransitionRoot) {
transitions.forEach(transition => {
addTransitionProgressCallbackToPendingTransition(
transition,
pendingBoundaries,
);
});
}
}
}
});
}
}
}
}
11. 隐藏或显示所有子项
function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) {
if (!supportsMutation) {
return;
}
// Finds the nearest host component children and updates their visibility
// to either hidden or visible.
// 查找最近的宿主组件子元素并更新它们的可见性设置为隐藏或可见。
let child = parentFiber.child;
while (child !== null) {
hideOrUnhideAllChildrenOnFiber(child, isHidden);
child = child.sibling;
}
}
12. 分离纤维变异
function detachFiberMutation(fiber: Fiber) {
// Cut off the return pointer to disconnect it from the tree.
// This enables us to detect and warn against state updates on an unmounted component.
// It also prevents events from bubbling from within disconnected components.
//
// 切断返回指针以将其从树中断开。
// 这使我们能够检测并警告在已卸载组件上进行状态更新的情况。
// 它还可以防止事件从已断开的组件中冒泡。
//
// Ideally, we should also clear the child pointer of the parent alternate to let this
// get GC:ed but we don't know which for sure which parent is the current
// one so we'll settle for GC:ing the subtree of this child.
// This child itself will be GC:ed when the parent updates the next time.
//
// 理想情况下,我们还应清除父替代的子指针,以便此事发生垃圾回收,但我们不确定哪家是现在的父级
// 一是这样我们就接受这个子树的回收。当父节点下次更新时,这个子节点会被垃圾回收。
//
// Note that we can't clear child or sibling pointers yet.
// They're needed for passive effects and for findDOMNode.
// We defer those fields, and all other cleanup, to the
// passive phase (see detachFiberAfterEffects).
//
// 注意我们暂时无法清除子节点或兄弟节点指针。
// 它们在被动效果和 findDOMNode 中是需要的。
// 我们将这些字段以及所有其他清理工作推迟到被动阶段(参见 detachFiberAfterEffects)。
//
// Don't reset the alternate yet, either. We need that so we can detach the
// alternate's fields in the passive phase. Clearing the return pointer is
// sufficient for findDOMNode semantics.
//
// 暂时也不要重置备用节点。我们需要它,以便在被动阶段分离备用节点的字段。
// 清空返回指针对于 findDOMNode 的语义来说已经足够了。
const alternate = fiber.alternate;
if (alternate !== null) {
alternate.return = null;
}
fiber.return = null;
}
13. 在效果之后分离 Fiber
备注
detachDeletedInstance()由宿主环境提供
function detachFiberAfterEffects(fiber: Fiber) {
const alternate = fiber.alternate;
if (alternate !== null) {
fiber.alternate = null;
detachFiberAfterEffects(alternate);
}
// Clear cyclical Fiber fields. This level alone is designed to roughly
// approximate the planned Fiber refactor. In that world, `setState` will be
// bound to a special "instance" object instead of a Fiber. The Instance
// object will not have any of these fields. It will only be connected to
// the fiber tree via a single link at the root. So if this level alone is
// sufficient to fix memory issues, that bodes well for our plans.
//
// 清理循环的 Fiber 字段。仅此级别的设计大致模拟了计划中的 Fiber 重构。在那个情况下,
// `setState` 将绑定到一个特殊的“实例”对象,而不是 Fiber。实例对象将不会有这些字段。它
// 只会通过根节点的单个链接与 fiber 树连接。因此,如果仅此级别就能解决内存问题,这对我们
// 的计划是个好兆头。
fiber.child = null;
fiber.deletions = null;
fiber.sibling = null;
// The `stateNode` is cyclical because on host nodes it points to the host
// tree, which has its own pointers to children, parents, and siblings.
// The other host nodes also point back to fibers, so we should detach that
// one, too.
//
// `stateNode` 是循环的,因为在宿主节点上它指向宿主树,宿主树自身有指向子节点、父节点和
// 兄弟节点的指针。
// 其他宿主节点也指向 fibers,所以我们也应该断开那个指针。
if (fiber.tag === HostComponent) {
const hostInstance: Instance = fiber.stateNode;
if (hostInstance !== null) {
detachDeletedInstance(hostInstance);
}
}
fiber.stateNode = null;
if (__DEV__) {
fiber._debugOwner = null;
}
// Theoretically, nothing in here should be necessary, because we already
// disconnected the fiber from the tree. So even if something leaks this
// particular fiber, it won't leak anything else.
//
// 从理论上讲,这里不应该有任何必要的操作,因为我们已经将 fiber 从树中断开了。所以即使
// 某些东西泄漏了这个特定的 fiber,它也不会泄漏其他任何东西。
fiber.return = null;
fiber.dependencies = null;
fiber.memoizedProps = null;
fiber.memoizedState = null;
fiber.pendingProps = null;
fiber.stateNode = null;
// TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
// 待办:改为使用 `commitPassiveUnmountInsideDeletedTreeOnFiber`。
fiber.updateQueue = null;
}
14. 提交删除效果
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现isSingletonScope()由宿主环境提供logComponentUnmount()由 ReactFiberPerformanceTrack#logComponentUnmount 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现
function commitDeletionEffects(
root: FiberRoot,
returnFiber: Fiber,
deletedFiber: Fiber,
) {
const prevEffectStart = pushComponentEffectStart();
if (supportsMutation) {
// We only have the top Fiber that was deleted but we need to recurse down its
// children to find all the terminal nodes.
//
// 我们只有被删除的顶层 Fiber,但我们需要递归查找它的子节点,以找到所有终端节点。
// Recursively delete all host nodes from the parent, detach refs, clean
// up mounted layout effects, and call componentWillUnmount.
//
// 递归删除父节点中的所有宿主节点,分离引用,清理已挂载的布局副作用,并
// 调用 componentWillUnmount。
// We only need to remove the topmost host child in each branch. But then we
// still need to keep traversing to unmount effects, refs, and cWU. TODO: We
// could split this into two separate traversals functions, where the second
// one doesn't include any removeChild logic. This is maybe the same
// function as "disappearLayoutEffects" (or whatever that turns into after
// the layout phase is refactored to use recursion).
//
// 我们只需要移除每个分支中最顶部的 host 子节点。但之后我们仍然需要继续遍历以卸载
// 副作用、refs 和 cWU。TODO:我们可以将其拆分为两个独立的遍历函数,第二个函数不
// 包含任何 removeChild 逻辑。这个可能就是“disappearLayoutEffects”函数(或者
// 在布局阶段重构为使用递归后它的最终形式)。
// Before starting, find the nearest host parent on the stack so we know
// which instance/container to remove the children from.
// TODO: Instead of searching up the fiber return path on every deletion, we
// can track the nearest host component on the JS stack as we traverse the
// tree during the commit phase. This would make insertions faster, too.
//
// 在开始之前,先在堆栈上找到最近的宿主父节点,这样我们就知道从哪个实例/容器中移除子节点。
// TODO:与其在每次删除时沿 fiber 返回路径向上搜索,我们可以在提交阶段遍历树的过程中,
// 在 JS 堆栈上追踪最近的宿主组件。这也会使插入操作更快。
let parent: null | Fiber = returnFiber;
findParent: while (parent !== null) {
switch (parent.tag) {
case HostSingleton: {
if (supportsSingletons) {
if (isSingletonScope(parent.type)) {
hostParent = parent.stateNode;
hostParentIsContainer = false;
break findParent;
}
break;
}
// Expected fallthrough when supportsSingletons is false
// 当 supportsSingletons 为 false 时,预期会继续执行下一个分支
}
case HostComponent: {
hostParent = parent.stateNode;
hostParentIsContainer = false;
break findParent;
}
case HostRoot:
case HostPortal: {
hostParent = parent.stateNode.containerInfo;
hostParentIsContainer = true;
break findParent;
}
}
parent = parent.return;
}
if (hostParent === null) {
throw new Error(
'Expected to find a host parent. This error is likely caused by ' +
'a bug in React. Please file an issue.',
);
}
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
hostParent = null;
hostParentIsContainer = false;
} else {
// Detach refs and call componentWillUnmount() on the whole subtree.
// 分离引用并在整个子树上调用 componentWillUnmount()。
commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(deletedFiber.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentUnmount(
deletedFiber,
componentEffectStartTime,
componentEffectEndTime,
);
}
popComponentEffectStart(prevEffectStart);
detachFiberMutation(deletedFiber);
}
15. 递归遍历删除影响
function recursivelyTraverseDeletionEffects(
finishedRoot: FiberRoot,
nearestMountedAncestor: Fiber,
parent: Fiber,
) {
// TODO: Use a static flag to skip trees that don't have unmount effects
// 待办:使用静态标志跳过没有卸载副作用的树
let child = parent.child;
while (child !== null) {
commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
child = child.sibling;
}
}
16. 提交删除对 Fiber 的影响
备注
onCommitUnmount()由 ReactFiberDevToolsHook#onCommitUnmount 实现pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现safelyDetachRef()由 ReactFiberCommitEffects#safelyDetachRef 实现releaseResource()由宿主环境提供unmountHoistable()由宿主环境提供isSingletonScope()由宿主环境提供commitHostSingletonRelease()由 ReactFiberCommitHostEffects#commitHostSingletonRelease 实现commitFragmentInstanceDeletionEffects()由 ReactFiberCommitHostEffects#commitFragmentInstanceDeletionEffects 实现commitHostRemoveChildFromContainer()由 ReactFiberCommitHostEffects#commitHostRemoveChildFromContainer 实现commitHostRemoveChild()由 ReactFiberCommitHostEffects#commitHostRemoveChild 实现captureCommitPhaseError()由 ReactFiberWorkLoop 提供clearSuspenseBoundaryFromContainer()由宿主环境提供clearSuspenseBoundary()由宿主环境提供commitHostPortalContainerChildren()由 ReactFiberCommitHostEffects#commitHostPortalContainerChildren 实现commitHookEffectListUnmount()由 ReactFiberCommitEffects#commitHookEffectListUnmount 实现commitHookLayoutUnmountEffects()由 ReactFiberCommitEffects#commitHookLayoutUnmountEffects 实现safelyCallComponentWillUnmount()由 ReactFiberCommitEffects#safelyCallComponentWillUnmount 实现untrackNamedViewTransition()由 ReactFiberDuplicateViewTransitions#trackNamedViewTransition 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现
function commitDeletionEffectsOnFiber(
finishedRoot: FiberRoot,
nearestMountedAncestor: Fiber,
deletedFiber: Fiber,
) {
// TODO: Delete this Hook once new DevTools ships everywhere. No longer needed.
// 待办:一旦新的开发工具全面发布,就删除这个 Hook。不再需要。
onCommitUnmount(deletedFiber);
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
// The cases in this outer switch modify the stack before they traverse
// into their subtree. There are simpler cases in the inner switch
// that don't modify the stack.
//
// 外层 switch 中的情况在进入子树之前会修改堆栈。
// 内层 switch 中有一些更简单的情况,不会修改堆栈。
switch (deletedFiber.tag) {
case HostHoistable: {
if (supportsResources) {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
if (deletedFiber.memoizedState) {
releaseResource(deletedFiber.memoizedState);
} else if (deletedFiber.stateNode) {
unmountHoistable(deletedFiber.stateNode);
}
break;
}
// Fall through
// 贯穿
}
case HostSingleton: {
if (supportsSingletons) {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}
const prevHostParent = hostParent;
const prevHostParentIsContainer = hostParentIsContainer;
if (isSingletonScope(deletedFiber.type)) {
hostParent = deletedFiber.stateNode;
hostParentIsContainer = false;
}
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
// Normally this is called in passive unmount effect phase however with
// HostSingleton we warn if you acquire one that is already associated to
// a different fiber. To increase our chances of avoiding this, specifically
// if you keyed a HostSingleton so there will be a delete followed by a Placement
// we treat detach eagerly here
//
// 通常这会在被动卸载效果阶段调用,然而对于 HostSingleton,如果你获取了一个已经与不同 fiber 关
// 联的实例,我们会发出警告。为了提高避免这种情况的可能性,特别是如果你给 HostSingleton 设置
// 了 key,那么将会先执行删除操作再执行 Placement,我们在这里会主动处理分离操作
commitHostSingletonRelease(deletedFiber);
hostParent = prevHostParent;
hostParentIsContainer = prevHostParentIsContainer;
break;
}
// Fall through
// 贯穿
}
case HostComponent: {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}
if (enableFragmentRefs && deletedFiber.tag === HostComponent) {
commitFragmentInstanceDeletionEffects(deletedFiber);
}
// Intentional fallthrough to next branch
// 有意贯穿到下一个分支
}
case HostText: {
// We only need to remove the nearest host child. Set the host parent
// to `null` on the stack to indicate that nested children don't
// need to be removed.
//
// 我们只需要移除最近的宿主子节点。在栈上将宿主父节点设置为 `null`,
// 以表明嵌套的子节点不需要被移除。
if (supportsMutation) {
const prevHostParent = hostParent;
const prevHostParentIsContainer = hostParentIsContainer;
hostParent = null;
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
hostParent = prevHostParent;
hostParentIsContainer = prevHostParentIsContainer;
if (hostParent !== null) {
// Now that all the child effects have unmounted, we can remove the
// node from the tree.
//
// 现在所有子效果都已卸载,我们可以将节点从树中移除了。
if (hostParentIsContainer) {
commitHostRemoveChildFromContainer(
deletedFiber,
nearestMountedAncestor,
hostParent as any as Container,
deletedFiber.stateNode as Instance | TextInstance,
);
} else {
commitHostRemoveChild(
deletedFiber,
nearestMountedAncestor,
hostParent as any as Instance,
deletedFiber.stateNode as Instance | TextInstance,
);
}
}
} else {
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
}
break;
}
case DehydratedFragment: {
if (enableSuspenseCallback) {
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
if (hydrationCallbacks !== null) {
try {
const onDeleted = hydrationCallbacks.onDeleted;
if (onDeleted) {
onDeleted(
deletedFiber.stateNode as SuspenseInstance | ActivityInstance,
);
}
} catch (error) {
captureCommitPhaseError(
deletedFiber,
nearestMountedAncestor,
error,
);
}
}
}
// Dehydrated fragments don't have any children
// 脱水的片段没有任何子元素
// Delete the dehydrated suspense boundary and all of its content.
// 删除脱水的挂起边界及其所有内容。
if (supportsMutation) {
if (hostParent !== null) {
if (hostParentIsContainer) {
clearSuspenseBoundaryFromContainer(
hostParent as any as Container,
deletedFiber.stateNode as SuspenseInstance,
);
} else {
clearSuspenseBoundary(
hostParent as any as Instance,
deletedFiber.stateNode as SuspenseInstance,
);
}
}
}
break;
}
case HostPortal: {
if (supportsMutation) {
// When we go into a portal, it becomes the parent to remove from.
// 当我们进入一个传送门时,它会成为要移除的父对象。
const prevHostParent = hostParent;
const prevHostParentIsContainer = hostParentIsContainer;
hostParent = deletedFiber.stateNode.containerInfo;
hostParentIsContainer = true;
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
hostParent = prevHostParent;
hostParentIsContainer = prevHostParentIsContainer;
} else {
if (supportsPersistence) {
commitHostPortalContainerChildren(
deletedFiber.stateNode,
deletedFiber,
createContainerChildSet(),
);
}
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
}
break;
}
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
if (
enableHiddenSubtreeInsertionEffectCleanup ||
!offscreenSubtreeWasHidden
) {
// TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.
// TODO: 使用 commitHookInsertionUnmountEffects 包装器来记录时间。
commitHookEffectListUnmount(
HookInsertion,
deletedFiber,
nearestMountedAncestor,
);
}
if (!offscreenSubtreeWasHidden) {
commitHookLayoutUnmountEffects(
deletedFiber,
nearestMountedAncestor,
HookLayout,
);
}
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
break;
}
case ClassComponent: {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
const instance = deletedFiber.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
safelyCallComponentWillUnmount(
deletedFiber,
nearestMountedAncestor,
instance,
);
}
}
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
break;
}
case ScopeComponent: {
if (enableScopeAPI) {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}
}
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
break;
}
case OffscreenComponent: {
if (disableLegacyMode || deletedFiber.mode & ConcurrentMode) {
// If this offscreen component is hidden, we already unmounted it. Before
// deleting the children, track that it's already unmounted so that we
// don't attempt to unmount the effects again.
//
// 如果这个屏幕外组件被隐藏,我们已经卸载了它。在删除子组件之前,记录它已经被卸载,这样
// 我们就不会再次尝试卸载效果。
//
// TODO: If the tree is hidden, in most cases we should be able to skip
// over the nested children entirely. An exception is we haven't yet found
// the topmost host node to delete, which we already track on the stack.
// But the other case is portals, which need to be detached no matter how
// deeply they are nested. We should use a subtree flag to track whether a
// subtree includes a nested portal.
//
// 待办:如果树是隐藏的,在大多数情况下我们应该能够完全跳过嵌套的子节点。一个例外是我们还
// 没有找到要删除的最顶层宿主节点,这已经在堆栈上跟踪了。但另一种情况是门户节点,无论它们
// 嵌套得多深都需要分离。我们应该使用子树标志来跟踪子树是否包含嵌套的门户节点。
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
offscreenSubtreeWasHidden =
prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
} else {
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
if (__DEV__) {
if (deletedFiber.flags & ViewTransitionNamedStatic) {
untrackNamedViewTransition(deletedFiber);
}
}
safelyDetachRef(deletedFiber, nearestMountedAncestor);
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
break;
}
// Fallthrough
// 贯穿
}
case Fragment: {
if (enableFragmentRefs) {
if (!offscreenSubtreeWasHidden) {
safelyDetachRef(deletedFiber, nearestMountedAncestor);
}
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
break;
}
// Fallthrough
// 贯穿
}
default: {
recursivelyTraverseDeletionEffects(
finishedRoot,
nearestMountedAncestor,
deletedFiber,
);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(deletedFiber.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
(componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
) {
logComponentEffect(
deletedFiber,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectErrors(prevEffectErrors);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
}
17. 提交悬而未决的回调
function commitSuspenseCallback(finishedWork: Fiber) {
// TODO: Delete this feature. It's not properly covered by DEV features.
// 待办:删除此功能。它没有被开发功能正确覆盖。
const newState: SuspenseState | null = finishedWork.memoizedState;
if (enableSuspenseCallback && newState !== null) {
const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;
if (typeof suspenseCallback === 'function') {
const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;
if (retryQueue !== null) {
suspenseCallback(new Set(retryQueue));
}
} else if (__DEV__) {
if (suspenseCallback !== undefined) {
console.error('Unexpected type for suspenseCallback.');
}
}
}
}
18. 提交活动水合回调
备注
commitHostHydratedActivity()由 ReactFiberCommitHostEffects#commitHostHydratedActivity 实现captureCommitPhaseError()由 ReactFiberWorkLoop 提供
function commitActivityHydrationCallbacks(
finishedRoot: FiberRoot,
finishedWork: Fiber,
) {
if (!supportsHydration) {
return;
}
const newState: ActivityState | null = finishedWork.memoizedState;
if (newState === null) {
const current = finishedWork.alternate;
if (current !== null) {
const prevState: ActivityState | null = current.memoizedState;
if (prevState !== null) {
const activityInstance = prevState.dehydrated;
commitHostHydratedActivity(activityInstance, finishedWork);
if (enableSuspenseCallback) {
try {
// TODO: Delete this feature. It's not properly covered by DEV features.
// TODO: 删除此功能。它没有被 DEV 功能正确覆盖。
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
if (hydrationCallbacks !== null) {
const onHydrated = hydrationCallbacks.onHydrated;
if (onHydrated) {
onHydrated(activityInstance);
}
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
}
}
}
19. 提交暂挂水合回调
备注
commitHostHydratedSuspense()由 ReactFiberCommitHostEffects#commitHostHydratedSuspense 实现captureCommitPhaseError()由 ReactFiberWorkLoop 提供
function commitSuspenseHydrationCallbacks(
finishedRoot: FiberRoot,
finishedWork: Fiber,
) {
if (!supportsHydration) {
return;
}
const newState: SuspenseState | null = finishedWork.memoizedState;
if (newState === null) {
const current = finishedWork.alternate;
if (current !== null) {
const prevState: SuspenseState | null = current.memoizedState;
if (prevState !== null) {
const suspenseInstance = prevState.dehydrated;
if (suspenseInstance !== null) {
commitHostHydratedSuspense(suspenseInstance, finishedWork);
if (enableSuspenseCallback) {
try {
// TODO: Delete this feature. It's not properly covered by DEV features.
// TODO: 删除此功能。它没有被 DEV 功能正确覆盖。
const hydrationCallbacks = finishedRoot.hydrationCallbacks;
if (hydrationCallbacks !== null) {
const onHydrated = hydrationCallbacks.onHydrated;
if (onHydrated) {
onHydrated(suspenseInstance);
}
}
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
}
}
}
}
}
20. 获取重试缓存
function getRetryCache(finishedWork: Fiber) {
// TODO: Unify the interface for the retry cache so we don't have to switch
// on the tag like this.
// 待办事项:统一重试缓存的接口,这样我们就不需要像这样根据标签切换了。
switch (finishedWork.tag) {
case ActivityComponent:
case SuspenseComponent:
case SuspenseListComponent: {
let retryCache = finishedWork.stateNode;
if (retryCache === null) {
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
}
return retryCache;
}
case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
let retryCache: null | Set<Wakeable> | WeakSet<Wakeable> =
instance._retryCache;
if (retryCache === null) {
retryCache = instance._retryCache = new PossiblyWeakSet();
}
return retryCache;
}
default: {
throw new Error(
`Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +
'bug in React.',
);
}
}
}
21. 附加暂挂重试监听器
备注
restorePendingUpdaters()由 ReactFiberWorkLoop 提供resolveRetryWakeable()由 ReactFiberWorkLoop 提供
function attachSuspenseRetryListeners(
finishedWork: Fiber,
wakeables: RetryQueue,
) {
// If this boundary just timed out, then it will have a set of wakeables.
// For each wakeable, attach a listener so that when it resolves, React
// attempts to re-render the boundary in the primary (pre-timeout) state.
//
// 如果这个边界刚刚超时,它将会有一组可唤醒对象。
// 对于每个可唤醒对象,附加一个监听器,以便当它被解析时,React
// 尝试在主(超时前)状态下重新渲染边界。
const retryCache = getRetryCache(finishedWork);
wakeables.forEach(wakeable => {
// Memoize using the boundary fiber to prevent redundant listeners.
// 使用边界 fiber 进行缓存以防止重复的监听器。
if (!retryCache.has(wakeable)) {
retryCache.add(wakeable);
if (enableUpdaterTracking) {
if (isDevToolsPresent) {
if (inProgressLanes !== null && inProgressRoot !== null) {
// If we have pending work still, associate the original updaters with it.
// 如果我们还有未完成的工作,将原来的更新者与之关联。
restorePendingUpdaters(inProgressRoot, inProgressLanes);
} else {
throw Error(
'Expected finished root and lanes to be set. This is a bug in React.',
);
}
}
}
const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
wakeable.then(retry, retry);
}
});
}
22. 判定挂起边界正在被隐藏
// This function detects when a Suspense boundary goes from visible to hidden.
// It returns false if the boundary is already hidden.
// TODO: Use an effect tag.
//
// 该函数用于检测 Suspense 边界何时从可见变为隐藏。
// 如果边界已经隐藏,则返回 false。
// 待办事项:使用 effect 标签。
function isSuspenseBoundaryBeingHidden(
current: Fiber | null,
finishedWork: Fiber,
): boolean {
if (current !== null) {
const oldState: SuspenseState | null = current.memoizedState;
if (oldState === null || oldState.dehydrated !== null) {
const newState: SuspenseState | null = finishedWork.memoizedState;
return newState !== null && newState.dehydrated === null;
}
}
return false;
}
23. 递归遍历变更效应
function recursivelyTraverseMutationEffects(
root: FiberRoot,
parentFiber: Fiber,
lanes: Lanes,
) {
// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects have fired.
// 删除效果可以安排在任何 fiber 类型上。它们需要在子效果触发之前发生。
const deletions = parentFiber.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
commitDeletionEffects(root, parentFiber, childToDelete);
}
}
if (parentFiber.subtreeFlags & (MutationMask | Cloned)) {
let child = parentFiber.child;
while (child !== null) {
commitMutationEffectsOnFiber(child, root, lanes);
child = child.sibling;
}
}
}
24. 提交突变效果到 Fiber
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现commitHookEffectListUnmount()由 ReactFiberCommitEffects#commitHookEffectListUnmount 实现commitHookEffectListMount()由 ReactFiberCommitEffects#commitHookEffectListMount 实现commitHookLayoutUnmountEffects()由 ReactFiberCommitEffects#commitHookLayoutUnmountEffects 实现safelyDetachRef()由 ReactFiberCommitEffects#safelyDetachRef 实现deferHiddenCallbacks()由宿主环境提供hydrateHoistable()由宿主环境提供mountHoistable()由宿主环境提供acquireResource()由宿主环境提供unmountHoistable()由宿主环境提供releaseResource()由宿主环境提供commitHostUpdate()由 ReactFiberCommitHostEffects#commitHostUpdate 实现commitHostResetTextContent()由 ReactFiberCommitHostEffects#commitHostResetTextContent 实现pushNestedEffectDurations()由 ReactProfilerTimer#pushNestedEffectDurations 实现pushRootMutationContext()由 ReactFiberMutationTracking#pushRootMutationContext 实现getHoistableRoot()由宿主环境提供commitHostHydratedContainer()由 ReactFiberCommitHostEffects#commitHostHydratedContainer 实现commitHostRootContainerChildren()由 ReactFiberCommitHostEffects#commitHostRootContainerChildren 实现popNestedEffectDurations()由 ReactProfilerTimer#popNestedEffectDurations 实现popMutationContext()由 ReactFiberMutationTracking#popMutationContext 实现includesLoadingIndicatorLanes()由 ReactFiberLane#includesLoadingIndicatorLanes 实现markIndicatorHandled()由 ReactFiberRootScheduler#markIndicatorHandled 实现pushMutationContext()由 ReactFiberMutationTracking#pushMutationContext 实现commitHostPortalContainerChildren()由 ReactFiberCommitHostEffects#commitHostPortalContainerChildren 实现bubbleNestedEffectDurations()由 ReactProfilerTimer#bubbleNestedEffectDurations 实现markCommitTimeOfFallback()由 ReactFiberWorkLoop 提供captureCommitPhaseError()由 ReactFiberWorkLoop 提供logComponentReappeared()由 ReactFiberPerformanceTrack#logComponentReappeared 实现logComponentDisappeared()由 ReactFiberPerformanceTrack#logComponentDisappeared 实现getViewTransitionClassName()由 ReactFiberViewTransitionComponent#getViewTransitionClassName 实现prepareScopeUpdate()由宿主环境提供updateFragmentInstanceFiber()由宿主环境提供logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现logComponentMount()由 ReactFiberPerformanceTrack#logComponentMount 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
const current = finishedWork.alternate;
const flags = finishedWork.flags;
// The effect flag should be checked *after* we refine the type of fiber,
// because the fiber tag is more specific. An exception is any flag related
// to reconciliation, because those can be set on all fiber types.
//
// 应该在我们细化 fiber 类型 *之后* 检查 effect 标志,因为 fiber 标签更具体。
// 例外情况是与协调相关的任何标志,因为这些可以在所有 fiber 类型上设置。
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (flags & Update) {
commitHookEffectListUnmount(
HookInsertion | HookHasEffect,
finishedWork,
finishedWork.return,
);
// TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.
// TODO: 使用 commitHookInsertionUnmountEffects 包装器来记录时间。
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
commitHookLayoutUnmountEffects(
finishedWork,
finishedWork.return,
HookLayout | HookHasEffect,
);
}
break;
}
case ClassComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (flags & Ref) {
if (!offscreenSubtreeWasHidden && current !== null) {
safelyDetachRef(current, current.return);
}
}
if (flags & Callback && offscreenSubtreeIsHidden) {
const updateQueue: UpdateQueue<mixed> | null =
finishedWork.updateQueue as any;
if (updateQueue !== null) {
deferHiddenCallbacks(updateQueue);
}
}
break;
}
case HostHoistable: {
if (supportsResources) {
// We cast because we always set the root at the React root and so it cannot be
// null while we are processing mutation effects
//
// 我们进行渲染是因为我们总是将根节点设置在 React 根上,因此在处理变更效果时它不可能为 null
const hoistableRoot: HoistableRoot = currentHoistableRoot as any;
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (flags & Ref) {
if (!offscreenSubtreeWasHidden && current !== null) {
safelyDetachRef(current, current.return);
}
}
if (flags & Update) {
const currentResource =
current !== null ? current.memoizedState : null;
const newResource = finishedWork.memoizedState;
if (current === null) {
// We are mounting a new HostHoistable Fiber. We fork the mount
// behavior based on whether this instance is a Hoistable Instance
// or a Hoistable Resource
//
// 我们正在挂载一个新的可挂载 Fiber。我们根据该实例是可挂载实例
// 还是可挂载资源来分叉挂载行为
if (newResource === null) {
if (finishedWork.stateNode === null) {
finishedWork.stateNode = hydrateHoistable(
hoistableRoot,
finishedWork.type,
finishedWork.memoizedProps,
finishedWork,
);
} else {
mountHoistable(
hoistableRoot,
finishedWork.type,
finishedWork.stateNode,
);
}
} else {
finishedWork.stateNode = acquireResource(
hoistableRoot,
newResource,
finishedWork.memoizedProps,
);
}
} else if (currentResource !== newResource) {
// We are moving to or from Hoistable Resource, or between
// different Hoistable Resources
//
// 我们正在移动到可升降资源或从可升降资源移动,或者在不同的可升降资源之间移动
if (currentResource === null) {
if (current.stateNode !== null) {
unmountHoistable(current.stateNode);
}
} else {
releaseResource(currentResource);
}
if (newResource === null) {
mountHoistable(
hoistableRoot,
finishedWork.type,
finishedWork.stateNode,
);
} else {
acquireResource(
hoistableRoot,
newResource,
finishedWork.memoizedProps,
);
}
} else if (newResource === null && finishedWork.stateNode !== null) {
commitHostUpdate(
finishedWork,
finishedWork.memoizedProps,
current.memoizedProps,
);
}
}
break;
}
// Fall through
// 贯穿
}
case HostSingleton: {
if (supportsSingletons) {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (flags & Ref) {
if (!offscreenSubtreeWasHidden && current !== null) {
safelyDetachRef(current, current.return);
}
}
if (current !== null && flags & Update) {
const newProps = finishedWork.memoizedProps;
const oldProps = current.memoizedProps;
commitHostUpdate(finishedWork, newProps, oldProps);
}
break;
}
// Fall through
// 贯穿
}
case HostComponent: {
// We've hit a host component, so it's no longer a direct parent.
// 我们已经遇到了一个宿主环境组件,所以它不再是直接父组件。
const prevOffscreenDirectParentIsHidden = offscreenDirectParentIsHidden;
offscreenDirectParentIsHidden = false;
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
offscreenDirectParentIsHidden = prevOffscreenDirectParentIsHidden;
commitReconciliationEffects(finishedWork, lanes);
if (flags & Ref) {
if (!offscreenSubtreeWasHidden && current !== null) {
safelyDetachRef(current, current.return);
}
}
if (supportsMutation) {
// TODO: ContentReset gets cleared by the children during the commit
// phase. This is a refactor hazard because it means we must read
// flags the flags after `commitReconciliationEffects` has already run;
// the order matters. We should refactor so that ContentReset does not
// rely on mutating the flag during commit. Like by setting a flag
// during the render phase instead.
//
// TODO: ContentReset 会在提交阶段被子组件清除。
// 这是一个重构风险,因为这意味着我们必须在 `commitReconciliationEffects` 已经运行之后读取标志;
// 顺序很重要。我们应该重构,使 ContentReset 不依赖在提交期间修改标志。
// 可以通过在渲染阶段设置标志来实现。
if (finishedWork.flags & ContentReset) {
commitHostResetTextContent(finishedWork);
}
if (flags & Update) {
const instance: Instance = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
//
// 提交先前准备的工作。
// 对于 hydration,我们重用更新路径,但将 oldProps 视为 newProps。
// 在这种情况下,updatePayload 将包含实际的更改。
const newProps = finishedWork.memoizedProps;
const oldProps =
current !== null ? current.memoizedProps : newProps;
commitHostUpdate(finishedWork, newProps, oldProps);
}
}
if (flags & FormReset) {
needsFormReset = true;
if (__DEV__) {
if (finishedWork.type !== 'form') {
// Paranoid coding. In case we accidentally start using the
// FormReset bit for something else.
//
// 偏执式编码。以防我们不小心把 FormReset 位用于其他用途。
console.error(
'Unexpected host component type. Expected a form. This is a ' +
'bug in React.',
);
}
}
}
} else {
if (enableEagerAlternateStateNodeCleanup) {
if (supportsPersistence) {
if (finishedWork.alternate !== null) {
// `finishedWork.alternate.stateNode` is pointing to a stale shadow
// node at this point, retaining it and its subtree. To reclaim
// memory, point `alternate.stateNode` to new shadow node. This
// prevents shadow node from staying in memory longer than it
// needs to. The correct behaviour of this is checked by test in
// React Native: ShadowNodeReferenceCounter-itest.js#L150
//
// `finishedWork.alternate.stateNode` 此时指向一个过时的 shadow 节点,保
// 留了它及其子树。为了回收内存,应将 `alternate.stateNode` 指向新
// 的 shadow 节点。这可以防止 shadow 节点比需要的时间在内存中停留。
// React Native 中的测试会验证这一行为的正确性:
// ShadowNodeReferenceCounter-itest.js#L150
finishedWork.alternate.stateNode = finishedWork.stateNode;
}
}
}
}
break;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (flags & Update) {
if (supportsMutation) {
if (finishedWork.stateNode === null) {
throw new Error(
'This should have a text node initialized. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
}
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
//
// 对于 hydration(重新渲染),我们重复使用更新路径,但将 oldProps 当作 newProps。
// 在这种情况下,updatePayload 将包含实际的更改。
const oldText: string =
current !== null ? current.memoizedProps : newText;
commitHostTextUpdate(finishedWork, newText, oldText);
}
}
break;
}
case HostRoot: {
const prevProfilerEffectDuration = pushNestedEffectDurations();
pushRootMutationContext();
if (supportsResources) {
prepareToCommitHoistables();
const previousHoistableRoot = currentHoistableRoot;
currentHoistableRoot = getHoistableRoot(root.containerInfo);
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
currentHoistableRoot = previousHoistableRoot;
commitReconciliationEffects(finishedWork, lanes);
} else {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
}
if (flags & Update) {
if (supportsMutation && supportsHydration) {
if (current !== null) {
const prevRootState: RootState = current.memoizedState;
if (prevRootState.isDehydrated) {
commitHostHydratedContainer(root, finishedWork);
}
}
}
if (supportsPersistence) {
commitHostRootContainerChildren(root, finishedWork);
}
}
if (needsFormReset) {
// A form component requested to be reset during this commit. We do this
// after all mutations in the rest of the tree so that `defaultValue`
// will already be updated. This way you can update `defaultValue` using
// data sent by the server as a result of the form submission.
//
// 在此提交期间,请求重置的表单组件。我们在树的其余部分执行所有变更后执行此操作,以
// 便 `defaultValue` 已经被更新。这样,你就可以使用服务器发送的表单提交结果数据
// 来更新 `defaultValue`。
//
// Theoretically we could check finishedWork.subtreeFlags & FormReset,
// but the FormReset bit is overloaded with other flags used by other
// fiber types. So this extra variable lets us skip traversing the tree
// except when a form was actually submitted.
//
// 从理论上讲,我们可以检查 finishedWork.subtreeFlags & FormReset,
// 但是 FormReset 位被其他 fiber 类型使用的标志位复用。
// 因此,这个额外的变量让我们可以跳过遍历整个树,
// 除非表单确实被提交了。
needsFormReset = false;
recursivelyResetForms(finishedWork);
}
if (enableProfilerTimer && enableProfilerCommitHooks) {
root.effectDuration += popNestedEffectDurations(
prevProfilerEffectDuration,
);
}
popMutationContext(false);
if (
enableDefaultTransitionIndicator &&
rootMutationContext &&
includesLoadingIndicatorLanes(lanes)
) {
// This root had a mutation. Mark this root as having rendered a manual
// loading state.
//
// 这个根节点有一个突变。将此根节点标记为已呈现手动加载状态。
markIndicatorHandled(root);
}
break;
}
case HostPortal: {
// For the purposes of visibility toggling, the direct children of a
// portal are considered "children" of the nearest hidden
// OffscreenComponent, regardless of whether there are any host components
// in between them. This is because portals are not part of the regular
// host tree hierarchy; we can't assume that just because a portal's
// HostComponent parent in the React tree will also be a parent in the
// actual host tree. So we must hide all of them.
//
// 为了实现可见性切换,portal 的直接子元素被视为最近的隐藏 OffscreenComponent 的“子元素”,
// 无论它们之间是否有任何宿主组件。这是因为 portal 并不属于常规的宿主树层次结构;我们不能假设
// 仅因为 portal 的 React 树中的 HostComponent 父组件也是实际宿主树中的父组件。因此,我们
// 必须隐藏它们全部。
const prevOffscreenDirectParentIsHidden = offscreenDirectParentIsHidden;
offscreenDirectParentIsHidden = offscreenSubtreeIsHidden;
const prevMutationContext = pushMutationContext();
if (supportsResources) {
const previousHoistableRoot = currentHoistableRoot;
currentHoistableRoot = getHoistableRoot(
finishedWork.stateNode.containerInfo,
);
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
currentHoistableRoot = previousHoistableRoot;
} else {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
}
if (viewTransitionMutationContext && inUpdateViewTransition) {
// A Portal doesn't necessarily exist within the context of this subtree.
// Ideally we would track which React ViewTransition component nests the container
// but that's costly. Instead, we treat each Portal as if it's a new React root.
// Therefore any leaked mutation means that the root should animate.
//
// Portal 不一定存在于此子树的上下文中。
// 理想情况下,我们会追踪哪个 React ViewTransition 组件包含该容器
// 但那样成本很高。相反,我们将每个 Portal 当作一个新的 React 根节点。
// 因此,任何泄漏的变更都意味着该根节点应该执行动画。
rootViewTransitionAffected = true;
}
popMutationContext(prevMutationContext);
if (flags & Update) {
if (supportsPersistence) {
commitHostPortalContainerChildren(
finishedWork.stateNode,
finishedWork,
finishedWork.stateNode.pendingChildren,
);
}
}
break;
}
case Profiler: {
const prevProfilerEffectDuration = pushNestedEffectDurations();
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (enableProfilerTimer && enableProfilerCommitHooks) {
const profilerInstance = finishedWork.stateNode;
// Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a chance
// to read them first.
//
// 将布局效果持续时间传播到下一个最近的 Profiler 上层节点。
// 在下一次渲染之前不要重置这些值,以便 DevTools 有机会先读取它们。
profilerInstance.effectDuration += bubbleNestedEffectDurations(
prevProfilerEffectDuration,
);
}
break;
}
case ActivityComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (flags & Update) {
const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;
if (retryQueue !== null) {
finishedWork.updateQueue = null;
attachSuspenseRetryListeners(finishedWork, retryQueue);
}
}
break;
}
case SuspenseComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
// TODO: We should mark a flag on the Suspense fiber itself, rather than
// relying on the Offscreen fiber having a flag also being marked. The
// reason is that this offscreen fiber might not be part of the work-in-
// progress tree! It could have been reused from a previous render. This
// doesn't lead to incorrect behavior because we don't rely on the flag
// check alone; we also compare the states explicitly below. But for
// modeling purposes, we _should_ be able to rely on the flag check alone.
// So this is a bit fragile.
//
// TODO: 我们应该在 Suspense fiber 本身上标记一个标志,而不是依赖 Offscreen fiber 也
// 被标记。原因是这个 offscreen fiber 可能不属于正在进行的工作树!它可能是从之前的渲染中
// 复用的。这不会导致行为错误,因为我们不只是依赖标志检查;我们在下面还会显式比较状态。不过
// 在建模方面,我们 _应该_ 能仅靠标志检查。所以这有点脆弱。
//
// Also, all this logic could/should move to the passive phase so it
// doesn't block paint.
//
// 同时,所有这些逻辑可以/应该移动到被动阶段,这样就不会阻塞绘制。
const offscreenFiber: Fiber = finishedWork.child as any;
if (offscreenFiber.flags & Visibility) {
// Throttle the appearance and disappearance of Suspense fallbacks.
// 控制 Suspense 后备内容显示和隐藏的速度。
const isShowingFallback =
(finishedWork.memoizedState as SuspenseState | null) !== null;
const wasShowingFallback =
current !== null &&
(current.memoizedState as SuspenseState | null) !== null;
if (alwaysThrottleRetries) {
if (isShowingFallback !== wasShowingFallback) {
// A fallback is either appearing or disappearing.
// 回退要么出现,要么消失。
markCommitTimeOfFallback();
}
} else {
if (isShowingFallback && !wasShowingFallback) {
// Old behavior. Only mark when a fallback appears, not when
// it disappears.
// 旧行为。只有在回退出现时才标记,而不是在消失时标记。
markCommitTimeOfFallback();
}
}
}
if (flags & Update) {
try {
commitSuspenseCallback(finishedWork);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;
if (retryQueue !== null) {
finishedWork.updateQueue = null;
attachSuspenseRetryListeners(finishedWork, retryQueue);
}
}
break;
}
case OffscreenComponent: {
const newState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = newState !== null;
const wasHidden = current !== null && current.memoizedState !== null;
if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
// Before committing the children, track on the stack whether this
// offscreen subtree was already hidden, so that we don't unmount the
// effects again.
//
// 在提交子节点之前,在堆栈上追踪这个离屏子树是否已经被隐藏,这样我们就不会再次卸载效果。
const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
const prevOffscreenDirectParentIsHidden = offscreenDirectParentIsHidden;
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden || isHidden;
offscreenDirectParentIsHidden =
prevOffscreenDirectParentIsHidden || isHidden;
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || wasHidden;
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
offscreenDirectParentIsHidden = prevOffscreenDirectParentIsHidden;
offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
if (
// If this was the root of the reappear.
// 如果这是重新出现的根源。
wasHidden &&
!isHidden &&
!prevOffscreenSubtreeIsHidden &&
!prevOffscreenSubtreeWasHidden &&
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentReappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
} else {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
}
commitReconciliationEffects(finishedWork, lanes);
if (flags & Visibility) {
const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
// Track the current state on the Offscreen instance so we can
// read it during an event
//
// 在 Offscreen 实例上跟踪当前状态,以便我们在事件期间读取它
if (isHidden) {
offscreenInstance._visibility &= ~OffscreenVisible;
} else {
offscreenInstance._visibility |= OffscreenVisible;
}
const isUpdate = current !== null;
if (isHidden) {
// Only trigger disappear layout effects if:
// - This is an update, not first mount.
// - This Offscreen was not hidden before.
// - Ancestor Offscreen was not hidden in previous commit or in this commit
//
// 仅在以下情况下触发消失布局效果:
// - 这是更新,而不是首次挂载。
// - 这个 Offscreen 之前没有被隐藏。
// - 上一次提交或本次提交中,祖先 Offscreen 没有被隐藏
if (
isUpdate &&
!wasHidden &&
!offscreenSubtreeIsHidden &&
!offscreenSubtreeWasHidden
) {
if (
disableLegacyMode ||
(finishedWork.mode & ConcurrentMode) !== NoMode
) {
// Disappear the layout effects of all the children
// 消除所有子元素的布局效果
recursivelyTraverseDisappearLayoutEffects(finishedWork);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentDisappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
}
if (supportsMutation) {
// If it's trying to unhide but the parent is still hidden, then
// we should not unhide.
//
// 如果尝试取消隐藏,但父元素仍然隐藏,那么我们不应该取消隐藏。
if (isHidden || !offscreenDirectParentIsHidden) {
hideOrUnhideAllChildren(finishedWork, isHidden);
}
}
}
// TODO: Move to passive phase
// 待办事项:转入被动阶段
if (flags & Update) {
const offscreenQueue: OffscreenQueue | null =
finishedWork.updateQueue as any;
if (offscreenQueue !== null) {
const retryQueue = offscreenQueue.retryQueue;
if (retryQueue !== null) {
offscreenQueue.retryQueue = null;
attachSuspenseRetryListeners(finishedWork, retryQueue);
}
}
}
break;
}
case SuspenseListComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (flags & Update) {
const retryQueue: Set<Wakeable> | null =
finishedWork.updateQueue as any;
if (retryQueue !== null) {
finishedWork.updateQueue = null;
attachSuspenseRetryListeners(finishedWork, retryQueue);
}
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
if (flags & Ref) {
if (!offscreenSubtreeWasHidden && current !== null) {
safelyDetachRef(current, current.return);
}
}
const prevMutationContext = pushMutationContext();
const prevUpdate = inUpdateViewTransition;
const isViewTransitionEligible =
enableViewTransition &&
includesOnlyViewTransitionEligibleLanes(lanes);
const props = finishedWork.memoizedProps;
inUpdateViewTransition =
isViewTransitionEligible &&
getViewTransitionClassName(props.default, props.update) !== 'none';
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
if (isViewTransitionEligible) {
if (current === null) {
// This is a new mount. We should have handled this as part of the
// Placement effect or it is deeper inside a entering transition.
//
// 这是一个新的挂载。我们应该在布局效果中处理它,或者它位于进入过渡的更深层。
} else if (viewTransitionMutationContext) {
// Something mutated in this tree so we need to animate this regardless
// what the measurements say. We use the Update flag to track this.
// If diffing was done in the render phase, like we used, this could have
// been done in the render already.
//
// 这棵树中发生了某些变异,所以无论测量结果如何,我们都需要对其进行动画处理。
// 我们使用 Update 标志来跟踪这一点。
// 如果像我们使用的那样在渲染阶段进行了差异比较,这本可以在渲染时就完成。
finishedWork.flags |= Update;
}
}
inUpdateViewTransition = prevUpdate;
popMutationContext(prevMutationContext);
break;
}
break;
}
case ScopeComponent: {
if (enableScopeAPI) {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
// TODO: This is a temporary solution that allowed us to transition away
// from React Flare on www.
//
// TODO: 这是一个临时解决方案,让我们能够逐步脱离 www 上的 React Flare。
if (flags & Ref) {
if (!offscreenSubtreeWasHidden && current !== null) {
safelyDetachRef(finishedWork, finishedWork.return);
}
if (!offscreenSubtreeIsHidden) {
safelyAttachRef(finishedWork, finishedWork.return);
}
}
if (flags & Update) {
const scopeInstance = finishedWork.stateNode;
prepareScopeUpdate(scopeInstance, finishedWork);
}
}
break;
}
case Fragment:
if (enableFragmentRefs) {
if (current && current.stateNode !== null) {
updateFragmentInstanceFiber(finishedWork, current.stateNode);
}
}
// Fallthrough
// 贯穿
default: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork, lanes);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0
) {
if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
if (
// Insertion
// 插入
finishedWork.alternate === null &&
finishedWork.return !== null &&
finishedWork.return.alternate !== null &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
const isHydration = isHydratingParent(
finishedWork.return.alternate,
finishedWork.return,
);
if (!isHydration) {
logComponentMount(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectErrors(prevEffectErrors);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
}
25. 提交复合副作用
备注
commitHostPlacement()由 ReactFiberCommitHostEffects#commitHostPlacement 实现
function commitReconciliationEffects(
finishedWork: Fiber,
committedLanes: Lanes,
) {
// Placement effects (insertions, reorders) can be scheduled on any fiber
// type. They needs to happen after the children effects have fired, but
// before the effects on this fiber have fired.
//
// 插入和重新排序等位置效果可以在任何 fiber 类型上调度。
// 它们需要在子节点效果执行后发生,但在该 fiber 的效果执行之前。
const flags = finishedWork.flags;
if (flags & Placement) {
commitHostPlacement(finishedWork);
// Clear the "placement" from effect tag so that we know that this is
// inserted, before any life-cycles like componentDidMount gets called.
// TODO: findDOMNode doesn't rely on this any more but isMounted does
// and isMounted is deprecated anyway so we should be able to kill this.
//
// 从 effect 标签中清除 "placement",这样我们就知道这是插入的,
// 在像 componentDidMount 这样的生命周期调用之前。
// TODO: findDOMNode 不再依赖它,但 isMounted 依赖
// 而 isMounted 无论如何已经被弃用,所以我们应该可以删除它。
finishedWork.flags &= ~Placement;
}
if (flags & Hydrating) {
finishedWork.flags &= ~Hydrating;
}
}
26. 递归重置表单
function recursivelyResetForms(parentFiber: Fiber) {
if (parentFiber.subtreeFlags & FormReset) {
let child = parentFiber.child;
while (child !== null) {
resetFormOnFiber(child);
child = child.sibling;
}
}
}
27. 在 Fiber 上重置表单
备注
resetFormInstance()由宿主环境提供
function resetFormOnFiber(fiber: Fiber) {
recursivelyResetForms(fiber);
if (fiber.tag === HostComponent && fiber.flags & FormReset) {
const formInstance: FormInstance = fiber.stateNode;
resetFormInstance(formInstance);
}
}
28. 在变更效果后提交
export function commitAfterMutationEffects(
root: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
): void {
if (!enableViewTransition) {
// This phase is only used for view transitions.
// 这个阶段仅用于视图过渡。
return;
}
commitAfterMutationEffectsOnFiber(finishedWork, root, committedLanes);
}
29. 在突变效果之后递归遍历
备注
measureNestedViewTransitions()由 ReactFiberCommitViewTransitions#measureNestedViewTransitions 实现
function recursivelyTraverseAfterMutationEffects(
root: FiberRoot,
parentFiber: Fiber,
lanes: Lanes,
) {
// We need to visit the same nodes that we visited in the before mutation phase.
// 我们需要访问与变异前阶段访问的相同节点。
if (parentFiber.subtreeFlags & BeforeAndAfterMutationTransitionMask) {
let child = parentFiber.child;
while (child !== null) {
commitAfterMutationEffectsOnFiber(child, root, lanes);
child = child.sibling;
}
} else {
// Nothing has changed in this subtree, but the parent may have still affected
// its size and position. We need to measure this and if not, restore it to
// not animate.
//
// 这个子树中没有任何变化,但父元素可能仍然影响了它的大小和位置。我们需要对其进行测量,如果
// 没有变化,就将其恢复为无动画状态。
measureNestedViewTransitions(parentFiber, false);
}
}
30. 在 Fiber 上提交变异后的副作用
备注
commitEnterViewTransitions()由 ReactFiberCommitViewTransitions#commitEnterViewTransitions 实现pushViewTransitionCancelableScope()由 ReactFiberCommitViewTransitions#pushViewTransitionCancelableScope 实现cancelViewTransitionName()由宿主环境提供cancelRootViewTransitionName()由宿主环境提供popViewTransitionCancelableScope()由 ReactFiberCommitViewTransitions#popViewTransitionCancelableScope 实现measureUpdateViewTransition()由 ReactFiberCommitViewTransitions#measureUpdateViewTransition 实现scheduleViewTransitionEvent()由 ReactFiberWorkLoop 提供
function commitAfterMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes,
) {
const current = finishedWork.alternate;
if (current === null) {
// This is a newly inserted subtree. We can't use Placement flags to detect
// this since they get removed in the mutation phase. Usually it's not enough
// to just check current because that can also happen deeper in the same tree.
// However, since we don't need to visit newly inserted subtrees in AfterMutation
// we can just bail after we're done with the first one.
// The first ViewTransition inside a newly mounted tree runs an enter transition
// but other nested ones don't unless they have a named pair.
//
// 这是一个新插入的子树。我们不能使用 Placement 标志来检测它,因为这些标志会在变更阶段被移除。
// 通常,仅检查 current 并不足够,因为这种情况也可能在同一棵树的更深层发生。然而,由于我们不
// 需要在 AfterMutation 阶段访问新插入的子树,所以我们可以在处理完第一个之后直接退出。新
// 挂载的树中的第一个 ViewTransition 会运行进入过渡,但其他嵌套的不会,除非它们有命名配对。
commitEnterViewTransitions(finishedWork, false);
return;
}
switch (finishedWork.tag) {
case HostRoot: {
viewTransitionContextChanged = false;
rootViewTransitionNameCanceled = false;
pushViewTransitionCancelableScope();
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
if (!viewTransitionContextChanged && !rootViewTransitionAffected) {
// If we didn't leak any resizing out to the root, we don't have to transition
// the root itself. This means that we can now safely cancel any cancellations
// that bubbled all the way up.
//
// 如果我们没有将任何尺寸调整泄露到根节点,就不必对根节点本身进行过渡。
// 这意味着我们现在可以安全地取消所有一路冒泡上来的取消操作。
const cancelableChildren = viewTransitionCancelableChildren;
if (cancelableChildren !== null) {
for (let i = 0; i < cancelableChildren.length; i += 3) {
cancelViewTransitionName(
cancelableChildren[i] as any as Instance,
cancelableChildren[i + 1] as any as string,
cancelableChildren[i + 2] as any as Props,
);
}
}
// We also cancel the root itself.
// 我们也取消根本身。
cancelRootViewTransitionName(root.containerInfo);
rootViewTransitionNameCanceled = true;
}
popViewTransitionCancelableScope(null);
break;
}
case HostComponent: {
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
break;
}
case HostPortal: {
const prevContextChanged = viewTransitionContextChanged;
viewTransitionContextChanged = false;
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
if (viewTransitionContextChanged) {
// A Portal doesn't necessarily exist within the context of this subtree.
// Ideally we would track which React ViewTransition component nests the container
// but that's costly. Instead, we treat each Portal as if it's a new React root.
// Therefore any leaked resize of a child could affect the root so the root should animate.
// We only do this if the Portal is inside a ViewTransition and it is not disabled
// with update="none". Otherwise the Portal is considered not animating.
//
// Portal 不一定存在于此子树的上下文中。
// 理想情况下,我们会跟踪哪个 React ViewTransition 组件嵌套了该容器
// 但那样代价很高。相反,我们将每个 Portal 当作一个新的 React 根节点处理。
// 因此,任何子节点泄漏的尺寸变化都可能影响根节点,所以根节点应该执行动画。
// 我们只在 Portal 位于 ViewTransition 内且未通过 update="none" 禁用时执行此操作。
// 否则,Portal 被认为不执行动画。
rootViewTransitionAffected = true;
}
viewTransitionContextChanged = prevContextChanged;
break;
}
case OffscreenComponent: {
const isModernRoot =
disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;
if (isModernRoot) {
const isHidden = finishedWork.memoizedState !== null;
if (isHidden) {
// The Offscreen tree is hidden. Skip over its after mutation effects.
// 离屏树是隐藏的。跳过它的变异后的效果。
} else {
// The Offscreen tree is visible.
// 离屏树是可见的。
const wasHidden = current.memoizedState !== null;
if (wasHidden) {
commitEnterViewTransitions(finishedWork, false);
// If it was previous hidden then the children are treated as enter
// not updates so we don't need to visit these children.
//
// 如果之前是隐藏的,那么子节点会被视为进入
// 而不是更新,所以我们不需要访问这些子节点。
} else {
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
}
}
} else {
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
}
break;
}
case ViewTransitionComponent: {
const prevContextChanged = viewTransitionContextChanged;
const prevCancelableChildren = pushViewTransitionCancelableScope();
viewTransitionContextChanged = false;
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
if (viewTransitionContextChanged) {
finishedWork.flags |= Update;
}
const inViewport = measureUpdateViewTransition(
current,
finishedWork,
false,
);
if ((finishedWork.flags & Update) === NoFlags || !inViewport) {
// If this boundary didn't update, then we may be able to cancel its children.
// We bubble them up to the parent set to be determined later if we can cancel.
// Similarly, if old and new state was outside the viewport, we can skip it
// even if it did update.
//
// 如果这个边界没有更新,那么我们可能可以取消它的子节点。
// 我们将它们上升到父节点,稍后再确定是否可以取消。
// 同样地,如果旧状态和新状态都在视口之外,即使它确实更新了,我们也可以跳过它。
if (prevCancelableChildren === null) {
// Bubbling up this whole set to the parent.
// 将整个集合冒泡到父级。
} else {
// Merge with parent set.
// 与父集合并。
prevCancelableChildren.push.apply(
prevCancelableChildren,
viewTransitionCancelableChildren,
);
popViewTransitionCancelableScope(prevCancelableChildren);
}
// TODO: If this doesn't end up canceled, because a parent animates,
// then we should probably issue an event since this instance is part of it.
//
// 待办事项:如果这个操作最终没有被取消,因为父级在动画中,
// 那么我们可能应该触发一个事件,因为这个实例是其中的一部分。
} else {
const props: ViewTransitionProps = finishedWork.memoizedProps;
scheduleViewTransitionEvent(finishedWork, props.onUpdate);
// If this boundary did update, we cannot cancel its children so those are dropped.
// 如果这个边界已更新,我们无法取消它的子项,因此这些子项将被丢弃。
popViewTransitionCancelableScope(prevCancelableChildren);
}
if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {
// This boundary changed size in a way that may have caused its parent to
// relayout. We need to bubble this information up to the parent.
//
// 这个边界的尺寸发生了变化,可能导致其父级重新布局。
// 我们需要将这个信息传递给父级。
viewTransitionContextChanged = true;
} else {
// Otherwise, we restore it to whatever the parent had found so far.
// 否则,我们将其恢复为父节点目前找到的内容。
viewTransitionContextChanged = prevContextChanged;
}
break;
}
default: {
recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
break;
}
}
}
31. 递归遍历布局副作用
function recursivelyTraverseLayoutEffects(
root: FiberRoot,
parentFiber: Fiber,
lanes: Lanes,
) {
if (parentFiber.subtreeFlags & LayoutMask) {
let child = parentFiber.child;
while (child !== null) {
const current = child.alternate;
commitLayoutEffectOnFiber(root, current, child, lanes);
child = child.sibling;
}
}
}
32. 递归遍历消失布局效果
function recursivelyTraverseDisappearLayoutEffects(parentFiber: Fiber) {
// TODO (Offscreen) Check: subtreeflags & (RefStatic | LayoutStatic)
// 待办 (离屏) 检查: subtreeflags & (RefStatic | LayoutStatic)
let child = parentFiber.child;
while (child !== null) {
disappearLayoutEffects(child);
child = child.sibling;
}
}
33. 递归遍历重新出现的布局副作用
function recursivelyTraverseReappearLayoutEffects(
finishedRoot: FiberRoot,
parentFiber: Fiber,
includeWorkInProgressEffects: boolean,
) {
// This function visits both newly finished work and nodes that were re-used
// from a previously committed tree. We cannot check non-static flags if the
// node was reused.
//
// 这个函数会访问新完成的工作和以前已提交树中重用的节点。
// 如果节点被重用,我们无法检查非静态标志。
const childShouldIncludeWorkInProgressEffects =
includeWorkInProgressEffects &&
(parentFiber.subtreeFlags & LayoutMask) !== NoFlags;
// TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
// 待办(屏幕外)检查:flags & (RefStatic | LayoutStatic)
let child = parentFiber.child;
while (child !== null) {
const current = child.alternate;
reappearLayoutEffects(
finishedRoot,
current,
child,
childShouldIncludeWorkInProgressEffects,
);
child = child.sibling;
}
}
34. 提交离屏被动挂载效果
备注
retainCache()由 ReactFiberCacheComponent#retainCache 实现releaseCache()由 ReactFiberCacheComponent#releaseCache 实现
function commitOffscreenPassiveMountEffects(
current: Fiber | null,
finishedWork: Fiber,
instance: OffscreenInstance,
) {
let previousCache: Cache | null = null;
if (
current !== null &&
current.memoizedState !== null &&
current.memoizedState.cachePool !== null
) {
previousCache = current.memoizedState.cachePool.pool;
}
let nextCache: Cache | null = null;
if (
finishedWork.memoizedState !== null &&
finishedWork.memoizedState.cachePool !== null
) {
nextCache = finishedWork.memoizedState.cachePool.pool;
}
// Retain/release the cache used for pending (suspended) nodes.
// Note that this is only reached in the non-suspended/visible case:
// when the content is suspended/hidden, the retain/release occurs
// via the parent Suspense component (see case above).
//
// 保留/释放用于挂起(暂停)节点的缓存。
// 请注意,这仅在未挂起/可见的情况下才会执行:
// 当内容被挂起/隐藏时,保留/释放操作是通过父 Suspense 组件进行的(见上面的情况)。
if (nextCache !== previousCache) {
if (nextCache != null) {
retainCache(nextCache);
}
if (previousCache != null) {
releaseCache(previousCache);
}
}
if (enableTransitionTracing) {
// TODO: Pre-rendering should not be counted as part of a transition. We
// may add separate logs for pre-rendering, but it's not part of the
// primary metrics.
//
// TODO:预渲染不应被计入过渡的一部分。我们可能会为预渲染添加单独的日志,但它不是主要
// 指标的一部分。
const offscreenState: OffscreenState = finishedWork.memoizedState;
const queue: OffscreenQueue | null = finishedWork.updateQueue as any;
const isHidden = offscreenState !== null;
if (queue !== null) {
if (isHidden) {
const transitions = queue.transitions;
if (transitions !== null) {
transitions.forEach(transition => {
// Add all the transitions saved in the update queue during
// the render phase (ie the transitions associated with this boundary)
// into the transitions set.
//
// 将在渲染阶段保存到更新队列中的所有过渡(即与此边界关联的过渡)
// 添加到过渡集合中。
if (instance._transitions === null) {
instance._transitions = new Set();
}
instance._transitions.add(transition);
});
}
const markerInstances = queue.markerInstances;
if (markerInstances !== null) {
markerInstances.forEach(markerInstance => {
const markerTransitions = markerInstance.transitions;
// There should only be a few tracing marker transitions because
// they should be only associated with the transition that
// caused them
//
// 应该只有少量的跟踪标记转换,因为它们应该仅与导致它们的转换相关联
if (markerTransitions !== null) {
markerTransitions.forEach(transition => {
if (instance._transitions === null) {
instance._transitions = new Set();
} else if (instance._transitions.has(transition)) {
if (markerInstance.pendingBoundaries === null) {
markerInstance.pendingBoundaries = new Map();
}
if (instance._pendingMarkers === null) {
instance._pendingMarkers = new Set();
}
instance._pendingMarkers.add(markerInstance);
}
});
}
});
}
}
finishedWork.updateQueue = null;
}
commitTransitionProgress(finishedWork);
// TODO: Refactor this into an if/else branch
// 待办:将此重构为 if/else 分支
if (!isHidden) {
instance._transitions = null;
instance._pendingMarkers = null;
}
}
}
35. 提交缓存被动挂载效果
备注
retainCache()由 ReactFiberCacheComponent#retainCache 实现releaseCache()由 ReactFiberCacheComponent#releaseCache 实现
function commitCachePassiveMountEffect(
current: Fiber | null,
finishedWork: Fiber,
) {
let previousCache: Cache | null = null;
if (finishedWork.alternate !== null) {
previousCache = finishedWork.alternate.memoizedState.cache;
}
const nextCache = finishedWork.memoizedState.cache;
// Retain/release the cache. In theory the cache component
// could be "borrowing" a cache instance owned by some parent,
// in which case we could avoid retaining/releasing. But it
// is non-trivial to determine when that is the case, so we
// always retain/release.
//
// 保留/释放缓存。理论上,缓存组件可能“借用”某个父组件拥有的缓存实例,
// 在这种情况下,我们可以避免保留/释放。但要确定具体情况并非易事,
// 因此我们总是执行保留/释放操作。
if (nextCache !== previousCache) {
retainCache(nextCache);
if (previousCache != null) {
releaseCache(previousCache);
}
}
}
36. 提交跟踪标记被动挂载效果
备注
addMarkerCompleteCallbackToPendingTransition()由 ReactFiberWorkLoop 实现
function commitTracingMarkerPassiveMountEffect(finishedWork: Fiber) {
// Get the transitions that were initiatized during the render
// and add a start transition callback for each of them
// We will only call this on initial mount of the tracing marker
// only if there are no suspense children
//
// 获取在渲染期间初始化的过渡并为每个过渡添加一个开始过渡的回调我们只会在跟踪标记的
// 初始挂载时调用这个前提是没有 Suspense 子组件
const instance = finishedWork.stateNode;
if (instance.transitions !== null && instance.pendingBoundaries === null) {
addMarkerCompleteCallbackToPendingTransition(
finishedWork.memoizedProps.name,
instance.transitions,
);
instance.transitions = null;
instance.pendingBoundaries = null;
instance.aborts = null;
instance.name = null;
}
}
37. 递归遍历被动挂载效果
备注
includesOnlyViewTransitionEligibleLanes()由 ReactFiberLane#includesOnlyViewTransitionEligibleLanes 实现restoreNestedViewTransitions()由 ReactFiberCommitViewTransitions#restoreNestedViewTransitions 实现
function recursivelyTraversePassiveMountEffects(
root: FiberRoot,
parentFiber: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
// 仅用于性能分析。下一个 Fiber 或根节点完成的开始时间。
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
) {
const isViewTransitionEligible =
enableViewTransition &&
includesOnlyViewTransitionEligibleLanes(committedLanes);
// TODO: We could optimize this by marking these with the Passive subtree
// flag in the render phase.
//
// 待办:我们可以通过在渲染阶段用被动子树标记来优化这一点。
const subtreeMask = isViewTransitionEligible
? PassiveTransitionMask
: PassiveMask;
if (
parentFiber.subtreeFlags & subtreeMask ||
// If this subtree rendered with profiling this commit, we need to visit it to log it.
// 如果这个子树使用剖析渲染了此提交,我们需要访问它以进行记录。
(enableProfilerTimer &&
enableComponentPerformanceTrack &&
parentFiber.actualDuration !== 0 &&
(parentFiber.alternate === null ||
parentFiber.alternate.child !== parentFiber.child))
) {
let child = parentFiber.child;
while (child !== null) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
const nextSibling = child.sibling;
commitPassiveMountOnFiber(
root,
child,
committedLanes,
committedTransitions,
nextSibling !== null
? (nextSibling.actualStartTime as any as number)
: endTime,
);
child = nextSibling;
} else {
commitPassiveMountOnFiber(
root,
child,
committedLanes,
committedTransitions,
0,
);
child = child.sibling;
}
}
} else if (isViewTransitionEligible) {
// We are inside an updated subtree. Any mutations that affected the
// parent HostInstance's layout or set of children (such as reorders)
// might have also affected the positioning or size of the inner
// ViewTransitions. Therefore we need to restore those too.
//
// 我们在一个更新的子树内部。任何影响父级 HostInstance 的布局或子元素集合的
// 变更(例如重新排序)也可能影响内部 ViewTransitions 的位置或大小。因此我们
// 也需要恢复它们。
restoreNestedViewTransitions(parentFiber);
}
}
38. 在 Fiber 上提交被动挂载
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现pushDeepEquality()由 ReactFiberPerformanceTrack#pushDeepEquality 实现includesOnlyViewTransitionEligibleLanes()由 ReactFiberLane#includesOnlyViewTransitionEligibleLanes 实现restoreEnterOrExitViewTransitions()由 ReactFiberCommitViewTransitions#restoreEnterOrExitViewTransitions 实现logComponentRender()由 ReactFiberPerformanceTrack#logComponentRender 实现commitHookPassiveMountEffects()由 ReactFiberCommitEffects#commitHookPassiveMountEffects 实现logComponentErrored()由 ReactFiberPerformanceTrack#logComponentErrored 实现pushNestedEffectDurations()由 ReactProfilerTimer#pushNestedEffectDurations 实现restoreRootViewTransitionName()由宿主环境提供retainCache()由 ReactFiberCacheComponent#retainCache 实现releaseCache()由 ReactFiberCacheComponent#releaseCache 实现addTransitionStartCallbackToPendingTransition()由宿主环境提供clearTransitionsForLanes()由 ReactFiberLane#clearTransitionsForLanes 实现addTransitionCompleteCallbackToPendingTransition()由宿主环境提供popNestedEffectDurations()由 ReactProfilerTimer#popNestedEffectDurations 实现bubbleNestedEffectDurations()由 ReactProfilerTimer#bubbleNestedEffectDurations 实现commitProfilerPostCommit()由 ReactFiberCommitEffects#commitProfilerPostCommit 实现logComponentReappeared()由 ReactFiberPerformanceTrack#logComponentReappeared 实现restoreUpdateViewTransition()由 ReactFiberCommitViewTransitions#restoreUpdateViewTransition 实现logComponentMount()由 ReactFiberPerformanceTrack#logComponentMount 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现popDeepEquality()由 ReactFiberPerformanceTrack#popDeepEquality 实现
function commitPassiveMountOnFiber(
finishedRoot: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
// 仅用于性能分析。下一个 Fiber 或根节点完成的开始时间。
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
): void {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
const prevDeepEquality = pushDeepEquality();
const isViewTransitionEligible = enableViewTransition
? includesOnlyViewTransitionEligibleLanes(committedLanes)
: false;
if (
isViewTransitionEligible &&
finishedWork.alternate === null &&
// We can't use the Placement flag here because it gets reset earlier. Instead,
// we check if this is the root of the insertion by checking if the parent
// was previous existing.
//
// 我们不能在这里使用 Placement 标记,因为它之前会被重置。相反,我们通过检查父节点之前是否
// 存在来判断这是否是插入的根节点。
finishedWork.return !== null &&
finishedWork.return.alternate !== null
) {
// This was a new mount. This means we could've triggered an enter animation on
// the content. Restore the view transitions if there were any assigned in the
// snapshot phase.
//
// 这是一个新的挂载。这意味着我们可能已经在内容上触发了进入动画。
// 如果在快照阶段分配了任何视图过渡,请恢复它们。
restoreEnterOrExitViewTransitions(finishedWork);
}
// When updating this function, also update reconnectPassiveEffects, which does
// most of the same things when an offscreen tree goes from hidden -> visible,
// or when toggling effects inside a hidden tree.
//
// 在更新此函数时,也要更新 reconnectPassiveEffects,该函数在离屏树从
// 隐藏 -> 可见
// ,或在隐藏树中切换效果时,执行大部分相同的操作。
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// If this component rendered in Profiling mode (DEV or in Profiler component) then log its
// render time. We do this after the fact in the passive effect to avoid the overhead of this
// getting in the way of the render characteristics and avoid the overhead of unwinding
// uncommitted renders.
//
// 如果这个组件在性能分析模式下渲染(开发环境或在 Profiler 组件中),则记录它的渲染时间。我们在被动
// effect 中完成此操作,以避免影响渲染特性,并避免在回滚未提交的渲染时产生额外开销。
if (
enableProfilerTimer &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
(finishedWork.actualStartTime as any as number) > 0 &&
(finishedWork.flags & PerformedWork) !== NoFlags
) {
logComponentRender(
finishedWork,
finishedWork.actualStartTime as any as number,
endTime,
inHydratedSubtree,
committedLanes,
);
}
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
commitHookPassiveMountEffects(
finishedWork,
HookPassive | HookHasEffect,
);
}
break;
}
case ClassComponent: {
// If this component rendered in Profiling mode (DEV or in Profiler component) then log its
// render time. We do this after the fact in the passive effect to avoid the overhead of this
// getting in the way of the render characteristics and avoid the overhead of unwinding
// uncommitted renders.
//
// 如果这个组件在性能分析模式下渲染(开发模式或在 Profiler 组件中),则记录它的渲染时间。我们在被动 effect 中
// 完成此操作,以避免这种开销影响渲染特性,并避免在回滚未提交渲染时产生额外开销。
if (
enableProfilerTimer &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
(finishedWork.actualStartTime as any as number) > 0
) {
if ((finishedWork.flags & DidCapture) !== NoFlags) {
logComponentErrored(
finishedWork,
finishedWork.actualStartTime as any as number,
endTime,
// TODO: The captured values are all hidden inside the updater/callback closures so
// we can't get to the errors but they're there so we should be able to log them.
// 待办事项:捕获的值都隐藏在更新器/回调闭包内部,因此我们无法直接获取错误,但错误确实存在,所以
// 我们应该能够记录它们。
[],
);
} else if ((finishedWork.flags & PerformedWork) !== NoFlags) {
logComponentRender(
finishedWork,
finishedWork.actualStartTime as any as number,
endTime,
inHydratedSubtree,
committedLanes,
);
}
}
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
break;
}
case HostRoot: {
const prevProfilerEffectDuration = pushNestedEffectDurations();
const wasInHydratedSubtree = inHydratedSubtree;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// Detect if this was a hydration commit by look at if the previous state was
// dehydrated and this wasn't a forced client render.
//
// 通过查看之前的状态是否是脱水状态且这次不是强制客户端渲染来检测这是否是一次水合提交。
inHydratedSubtree =
finishedWork.alternate !== null &&
(finishedWork.alternate.memoizedState as RootState).isDehydrated &&
(finishedWork.flags & ForceClientRender) === NoFlags;
}
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (enableProfilerTimer && enableComponentPerformanceTrack) {
inHydratedSubtree = wasInHydratedSubtree;
}
if (isViewTransitionEligible) {
if (supportsMutation && rootViewTransitionNameCanceled) {
restoreRootViewTransitionName(finishedRoot.containerInfo);
}
}
if (flags & Passive) {
let previousCache: Cache | null = null;
if (finishedWork.alternate !== null) {
previousCache = finishedWork.alternate.memoizedState.cache;
}
const nextCache = finishedWork.memoizedState.cache;
// Retain/release the root cache.
// Note that on initial mount, previousCache and nextCache will be the same
// and this retain won't occur. To counter this, we instead retain the HostRoot's
// initial cache when creating the root itself (see createFiberRoot() in
// ReactFiberRoot.js). Subsequent updates that change the cache are reflected
// here, such that previous/next caches are retained correctly.
//
// 保留/释放根缓存。
// 注意,在初始挂载时,previousCache 和 nextCache 将是相同的,因此该保留操作不会发生。
// 为了解决这个问题,我们在创建根节点本身时(参见 ReactFiberRoot.js 中的
// createFiberRoot())保留 HostRoot 的初始缓存。随后更改缓存的更新会在这里反映,
// 这样 previous/next 缓存就能被正确保留。
if (nextCache !== previousCache) {
retainCache(nextCache);
if (previousCache != null) {
releaseCache(previousCache);
}
}
if (enableTransitionTracing) {
// Get the transitions that were initiatized during the render
// and add a start transition callback for each of them
// 获取在渲染期间初始化的过渡并为每个过渡添加一个开始过渡的回调
const root: FiberRoot = finishedWork.stateNode;
const incompleteTransitions = root.incompleteTransitions;
// Initial render
// 初始渲染
if (committedTransitions !== null) {
committedTransitions.forEach(transition => {
addTransitionStartCallbackToPendingTransition(transition);
});
clearTransitionsForLanes(finishedRoot, committedLanes);
}
incompleteTransitions.forEach((markerInstance, transition) => {
const pendingBoundaries = markerInstance.pendingBoundaries;
if (pendingBoundaries === null || pendingBoundaries.size === 0) {
if (markerInstance.aborts === null) {
addTransitionCompleteCallbackToPendingTransition(transition);
}
incompleteTransitions.delete(transition);
}
});
clearTransitionsForLanes(finishedRoot, committedLanes);
}
}
if (enableProfilerTimer && enableProfilerCommitHooks) {
finishedRoot.passiveEffectDuration += popNestedEffectDurations(
prevProfilerEffectDuration,
);
}
break;
}
case Profiler: {
// Only Profilers with work in their subtree will have a Passive effect scheduled.
// 只有其子树中有工作的分析器才会安排被动效果。
if (flags & Passive) {
const prevProfilerEffectDuration = pushNestedEffectDurations();
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
const profilerInstance = finishedWork.stateNode;
if (enableProfilerTimer && enableProfilerCommitHooks) {
// Bubble times to the next nearest ancestor Profiler.
// After we process that Profiler, we'll bubble further up.
//
// 将时间冒泡到下一个最近的宿主Profiler。在我们处理完该Profiler后,我们会
// 进一步向上冒泡。
profilerInstance.passiveEffectDuration += bubbleNestedEffectDurations(
prevProfilerEffectDuration,
);
}
commitProfilerPostCommit(
finishedWork,
finishedWork.alternate,
// This value will still reflect the previous commit phase.
// It does not get reset until the start of the next commit phase.
//
// 这个值仍会反映上一次提交阶段的情况。它不会被重置,直到下一次提交阶段开始。
commitStartTime,
profilerInstance.passiveEffectDuration,
);
} else {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
}
break;
}
case ActivityComponent: {
const wasInHydratedSubtree = inHydratedSubtree;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
const prevState: ActivityState | null =
finishedWork.alternate !== null
? finishedWork.alternate.memoizedState
: null;
const nextState: ActivityState | null = finishedWork.memoizedState;
if (prevState !== null && nextState === null) {
// This was dehydrated but is no longer dehydrated. We may have now either hydrated it
// or client rendered it.
//
// 这之前是脱水的,但现在不再脱水。我们可能已经把它重新水合了,或者是客户端渲染了它。
const deletions = finishedWork.deletions;
if (
deletions !== null &&
deletions.length > 0 &&
deletions[0].tag === DehydratedFragment
) {
// This was an abandoned hydration that deleted the dehydrated fragment. That means we
// are not hydrating this Suspense boundary.
//
// 这是一个被废弃的水合,它删除了脱水的片段。这意味着我们不会对这个 Suspense 边界进行水合
inHydratedSubtree = false;
const hydrationErrors = prevState.hydrationErrors;
// If there were no hydration errors, that suggests that this was an intentional client
// rendered boundary.
//
// 如果没有水合错误,这表明这是一个有意的客户端渲染边界。
if (hydrationErrors !== null) {
const startTime: number = finishedWork.actualStartTime as any;
logComponentErrored(
finishedWork,
startTime,
endTime,
hydrationErrors,
);
}
} else {
// If any children committed they were hydrated.
// 如果有任何子元素被提交,它们会被填充。
inHydratedSubtree = true;
}
} else {
inHydratedSubtree = false;
}
}
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (enableProfilerTimer && enableComponentPerformanceTrack) {
inHydratedSubtree = wasInHydratedSubtree;
}
break;
}
case SuspenseComponent: {
const wasInHydratedSubtree = inHydratedSubtree;
if (enableProfilerTimer && enableComponentPerformanceTrack) {
const prevState: SuspenseState | null =
finishedWork.alternate !== null
? finishedWork.alternate.memoizedState
: null;
const nextState: SuspenseState | null = finishedWork.memoizedState;
if (
prevState !== null &&
prevState.dehydrated !== null &&
(nextState === null || nextState.dehydrated === null)
) {
// This was dehydrated but is no longer dehydrated. We may have now either hydrated it
// or client rendered it.
// 这之前是脱水的,但现在不再脱水。我们可能已经把它重新水合了,或者是客户端渲染了它。
const deletions = finishedWork.deletions;
if (
deletions !== null &&
deletions.length > 0 &&
deletions[0].tag === DehydratedFragment
) {
// This was an abandoned hydration that deleted the dehydrated fragment. That means we
// are not hydrating this Suspense boundary.
//
// 这是一个被废弃的水合,它删除了脱水的片段。这意味着我们不会对这个 Suspense 边界进行水合
inHydratedSubtree = false;
const hydrationErrors = prevState.hydrationErrors;
// If there were no hydration errors, that suggests that this was an intentional client
// rendered boundary.
//
// 如果没有出现 hydration 错误,这表明这是一个有意的客户端渲染边界。
if (hydrationErrors !== null) {
const startTime: number = finishedWork.actualStartTime as any;
logComponentErrored(
finishedWork,
startTime,
endTime,
hydrationErrors,
);
}
} else {
// If any children committed they were hydrated.
// 如果有任何子组件提交,它们将被初始化。
inHydratedSubtree = true;
}
} else {
inHydratedSubtree = false;
}
}
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (enableProfilerTimer && enableComponentPerformanceTrack) {
inHydratedSubtree = wasInHydratedSubtree;
}
break;
}
case LegacyHiddenComponent: {
if (enableLegacyHidden) {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
const current = finishedWork.alternate;
const instance: OffscreenInstance = finishedWork.stateNode;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}
}
break;
}
case OffscreenComponent: {
// TODO: Pass `current` as argument to this function
// 待办:将 `current` 作为参数传递给此函数
const instance: OffscreenInstance = finishedWork.stateNode;
const current = finishedWork.alternate;
const nextState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = nextState !== null;
if (isHidden) {
if (
isViewTransitionEligible &&
current !== null &&
current.memoizedState === null
) {
// Content is now hidden but wasn't before. This means we could've
// triggered an exit animation on the content. Restore the view
// transitions if there were any assigned in the snapshot phase.
//
// 内容现在被隐藏了,但以前没有。这意味着我们可能已经在内容上触发了退出动画。如果
// 在快照阶段有分配过视图过渡,请恢复它们。
restoreEnterOrExitViewTransitions(current);
}
if (instance._visibility & OffscreenPassiveEffectsConnected) {
// The effects are currently connected. Update them.
// 当前效果已连接。请更新它们。
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
} else {
if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
// The effects are currently disconnected. Since the tree is hidden,
// don't connect them. This also applies to the initial render.
// "Atomic" effects are ones that need to fire on every commit,
// even during pre-rendering. An example is updating the reference
// count on cache instances.
//
// 这些效果目前是断开连接的。由于树是隐藏的,不要连接它们。这也适用于初始渲染。
// “原子(Atomic)”效果是指需要在每次提交时触发的效果,即使在预渲染期间也是
// 如此。例如,更新缓存实例的引用计数。
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
} else {
// Legacy Mode: Fire the effects even if the tree is hidden.
// 旧版模式:即使树隐藏也触发效果。
instance._visibility |= OffscreenPassiveEffectsConnected;
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
}
}
} else {
// Tree is visible
// 树是可见的
if (
isViewTransitionEligible &&
current !== null &&
current.memoizedState !== null
) {
// Content is now visible but wasn't before. This means we could've
// triggered an enter animation on the content. Restore the view
// transitions if there were any assigned in the snapshot phase.
//
// 内容现在可见,但之前不可见。这意味着我们可能已经对内容触发了进入动画。如果在快照
// 阶段分配了任何视图过渡,请恢复它们。
restoreEnterOrExitViewTransitions(finishedWork);
}
if (instance._visibility & OffscreenPassiveEffectsConnected) {
// The effects are currently connected. Update them.
// 当前效果已连接。请更新它们。
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
} else {
// The effects are currently disconnected. Reconnect them, while also
// firing effects inside newly mounted trees. This also applies to
// the initial render.
//
// 当前效果已断开连接。重新连接它们,同时在新挂载的树中触发效果。这也适用于初始渲染。
instance._visibility |= OffscreenPassiveEffectsConnected;
const includeWorkInProgressEffects =
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(enableProfilerTimer &&
enableComponentPerformanceTrack &&
finishedWork.actualDuration !== 0 &&
(finishedWork.alternate === null ||
finishedWork.alternate.child !== finishedWork.child));
recursivelyTraverseReconnectPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
includeWorkInProgressEffects,
endTime,
);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
!inHydratedSubtree
) {
// Log the reappear in the render phase.
// 在渲染阶段记录重新出现。
const startTime = finishedWork.actualStartTime as any as number;
if (startTime >= 0 && endTime - startTime > 0.05) {
logComponentReappeared(finishedWork, startTime, endTime);
}
if (
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentReappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
}
if (flags & Passive) {
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}
break;
}
case CacheComponent: {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
// TODO: Pass `current` as argument to this function
// 待办:将 `current` 作为参数传递给此函数
const current = finishedWork.alternate;
commitCachePassiveMountEffect(current, finishedWork);
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
if (isViewTransitionEligible) {
const current = finishedWork.alternate;
if (current === null) {
// This is a new mount. We should have handled this as part of the
// Placement effect or it is deeper inside a entering transition.
//
// 这是一个新的挂载。我们应该在布局效果中处理它,或者它位于进入过渡的更深层。
} else {
// Something mutated within this subtree. This might have caused
// something to cross-fade if we didn't already cancel it.
// If not, restore it.
//
// 这个子树中发生了一些变动。这可能导致某些内容进行交叉淡入淡出,如果我们还没有取消它。
// 如果没有,恢复它。
restoreUpdateViewTransition(current, finishedWork);
}
}
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
break;
}
// Fallthrough
// 贯穿
}
case TracingMarkerComponent: {
if (enableTransitionTracing) {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
commitTracingMarkerPassiveMountEffect(finishedWork);
}
break;
}
// Intentional fallthrough to next branch
// 有意贯穿到下一个分支
}
default: {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode
) {
const isMount =
!inHydratedSubtree &&
finishedWork.alternate === null &&
finishedWork.return !== null &&
finishedWork.return.alternate !== null;
if (isMount) {
// Log the mount in the render phase.
// 在渲染阶段记录挂载。
const startTime = finishedWork.actualStartTime as any as number;
if (startTime >= 0 && endTime - startTime > 0.05) {
logComponentMount(finishedWork, startTime, endTime);
}
}
if (componentEffectStartTime >= 0 && componentEffectEndTime >= 0) {
if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
if (isMount && componentEffectEndTime - componentEffectStartTime > 0.05) {
logComponentMount(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
}
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectErrors(prevEffectErrors);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
popDeepEquality(prevDeepEquality);
}
39. 递归遍历重新连接被动效果
function recursivelyTraverseReconnectPassiveEffects(
finishedRoot: FiberRoot,
parentFiber: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
includeWorkInProgressEffects: boolean,
endTime: number,
) {
// This function visits both newly finished work and nodes that were re-used
// from a previously committed tree. We cannot check non-static flags if the
// node was reused.
//
// 这个函数会访问新完成的工作和从之前提交的树中重用的节点。
// 如果节点是重用的,我们无法检查非静态标志。
const childShouldIncludeWorkInProgressEffects =
includeWorkInProgressEffects &&
((parentFiber.subtreeFlags & PassiveMask) !== NoFlags ||
(enableProfilerTimer &&
enableComponentPerformanceTrack &&
parentFiber.actualDuration !== 0 &&
(parentFiber.alternate === null ||
parentFiber.alternate.child !== parentFiber.child)));
// TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
// 待办(屏幕外)检查:flags & (RefStatic | LayoutStatic)
let child = parentFiber.child;
while (child !== null) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
const nextSibling = child.sibling;
reconnectPassiveEffects(
finishedRoot,
child,
committedLanes,
committedTransitions,
childShouldIncludeWorkInProgressEffects,
nextSibling !== null
? (nextSibling.actualStartTime as any as number)
: endTime,
);
child = nextSibling;
} else {
reconnectPassiveEffects(
finishedRoot,
child,
committedLanes,
committedTransitions,
childShouldIncludeWorkInProgressEffects,
endTime,
);
child = child.sibling;
}
}
}
40. 递归遍历原子被动效果
function recursivelyTraverseAtomicPassiveEffects(
finishedRoot: FiberRoot,
parentFiber: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
) {
// "Atomic" effects are ones that need to fire on every commit, even during
// pre-rendering. We call this function when traversing a hidden tree whose
// regular effects are currently disconnected.
// TODO: Add special flag for atomic effects
//
// “原子”效果是那些需要在每次提交时触发的效果,即使在预渲染期间也是如此。
// 当遍历一个其常规效果当前已断开连接的隐藏树时,我们会调用此函数。
// 待办:为原子效果添加特殊标志
if (
parentFiber.subtreeFlags & PassiveMask ||
(enableProfilerTimer &&
enableComponentPerformanceTrack &&
parentFiber.actualDuration !== 0 &&
(parentFiber.alternate === null ||
parentFiber.alternate.child !== parentFiber.child))
) {
let child = parentFiber.child;
while (child !== null) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
const nextSibling = child.sibling;
commitAtomicPassiveEffects(
finishedRoot,
child,
committedLanes,
committedTransitions,
nextSibling !== null
? (nextSibling.actualStartTime as any as number)
: endTime,
);
child = nextSibling;
} else {
commitAtomicPassiveEffects(
finishedRoot,
child,
committedLanes,
committedTransitions,
endTime,
);
child = child.sibling;
}
}
}
}
41. 提交原子被动效果
备注
pushDeepEquality()由 ReactFiberPerformanceTrack#pushDeepEquality 实现logComponentRender()由 ReactFiberPerformanceTrack#logComponentRender 实现popDeepEquality()由 ReactFiberPerformanceTrack#popDeepEquality 实现
function commitAtomicPassiveEffects(
finishedRoot: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
committedTransitions: Array<Transition> | null,
// 仅用于性能分析。下一个 Fiber 或根节点完成的开始时间。
endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
) {
const prevDeepEquality = pushDeepEquality();
// If this component rendered in Profiling mode (DEV or in Profiler component) then log its
// render time. A render can happen even if the subtree is offscreen.
//
// 如果此组件在性能分析模式下渲染(开发模式或在 Profiler 组件中),则记录其渲染时间。
// 即使子树在屏幕之外,渲染也可能发生。
if (
enableProfilerTimer &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
(finishedWork.actualStartTime as any as number) > 0 &&
(finishedWork.flags & PerformedWork) !== NoFlags
) {
logComponentRender(
finishedWork,
finishedWork.actualStartTime as any as number,
endTime,
inHydratedSubtree,
committedLanes,
);
}
// "Atomic" effects are ones that need to fire on every commit, even during
// pre-rendering. We call this function when traversing a hidden tree whose
// regular effects are currently disconnected.
//
// “原子”副作用是指需要在每次提交时触发的副作用,即使在预渲染期间也是如此。我们在遍历其
// 常规副作用当前已断开的隐藏树时调用此函数。
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case OffscreenComponent: {
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
// TODO: Pass `current` as argument to this function
// 待办:将 `current` 作为参数传递给此函数
const current = finishedWork.alternate;
const instance: OffscreenInstance = finishedWork.stateNode;
commitOffscreenPassiveMountEffects(current, finishedWork, instance);
}
break;
}
case CacheComponent: {
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
// TODO: Pass `current` as argument to this function
// 待办:将 `current` 作为参数传递给此函数
const current = finishedWork.alternate;
commitCachePassiveMountEffect(current, finishedWork);
}
break;
}
default: {
recursivelyTraverseAtomicPassiveEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
break;
}
}
popDeepEquality(prevDeepEquality);
}
42. 递归累积挂起提交
function recursivelyAccumulateSuspenseyCommit(
parentFiber: Fiber,
committedLanes: Lanes,
suspendedState: SuspendedState,
): void {
if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
let child = parentFiber.child;
while (child !== null) {
accumulateSuspenseyCommitOnFiber(child, committedLanes, suspendedState);
child = child.sibling;
}
}
}
43. 在 Fiber 上累积挂起提交
备注
suspendResource()由宿主环境提供includesOnlySuspenseyCommitEligibleLanes()由 ReactFiberLane#includesOnlyViewTransitionEligibleLanes 实现maySuspendCommitInSyncRender()由宿主环境提供suspendInstance()由宿主环境提供getHoistableRoot()由宿主环境提供trackAppearingViewTransition()由 ReactFiberCommitViewTransitions#trackAppearingViewTransition 实现
function accumulateSuspenseyCommitOnFiber(
fiber: Fiber,
committedLanes: Lanes,
suspendedState: SuspendedState,
) {
switch (fiber.tag) {
case HostHoistable: {
recursivelyAccumulateSuspenseyCommit(
fiber,
committedLanes,
suspendedState,
);
if (fiber.flags & suspenseyCommitFlag) {
if (fiber.memoizedState !== null) {
suspendResource(
suspendedState,
// This should always be set by visiting HostRoot first
// 这应该始终通过首先访问 HostRoot 来设置
currentHoistableRoot as any,
fiber.memoizedState,
fiber.memoizedProps,
);
} else {
const instance = fiber.stateNode;
const type = fiber.type;
const props = fiber.memoizedProps;
// TODO: Allow sync lanes to suspend too with an opt-in.
// TODO: 允许同步通道也可以挂起,并提供可选功能。
if (
includesOnlySuspenseyCommitEligibleLanes(committedLanes) ||
maySuspendCommitInSyncRender(type, props)
) {
suspendInstance(suspendedState, instance, type, props);
}
}
}
break;
}
case HostComponent: {
recursivelyAccumulateSuspenseyCommit(
fiber,
committedLanes,
suspendedState,
);
if (fiber.flags & suspenseyCommitFlag) {
const instance = fiber.stateNode;
const type = fiber.type;
const props = fiber.memoizedProps;
// TODO: Allow sync lanes to suspend too with an opt-in.
// TODO: 允许同步通道也可以挂起,并提供可选功能。
if (
includesOnlySuspenseyCommitEligibleLanes(committedLanes) ||
maySuspendCommitInSyncRender(type, props)
) {
suspendInstance(suspendedState, instance, type, props);
}
}
break;
}
case HostRoot:
case HostPortal: {
if (supportsResources) {
const previousHoistableRoot = currentHoistableRoot;
const container: Container = fiber.stateNode.containerInfo;
currentHoistableRoot = getHoistableRoot(container);
recursivelyAccumulateSuspenseyCommit(
fiber,
committedLanes,
suspendedState,
);
currentHoistableRoot = previousHoistableRoot;
} else {
recursivelyAccumulateSuspenseyCommit(
fiber,
committedLanes,
suspendedState,
);
}
break;
}
case OffscreenComponent: {
const isHidden = (fiber.memoizedState as OffscreenState | null) !== null;
if (isHidden) {
// Don't suspend in hidden trees
// 不要在隐蔽的树木中停留
} else {
const current = fiber.alternate;
const wasHidden =
current !== null &&
(current.memoizedState as OffscreenState | null) !== null;
if (wasHidden) {
// This tree is being revealed. Visit all newly visible suspensey
// instances, even if they're in the current tree.
//
// 这棵树正在被揭示。访问所有新显示的挂起实例,即使它们在当前树中。
const prevFlags = suspenseyCommitFlag;
suspenseyCommitFlag = MaySuspendCommit;
recursivelyAccumulateSuspenseyCommit(
fiber,
committedLanes,
suspendedState,
);
suspenseyCommitFlag = prevFlags;
} else {
recursivelyAccumulateSuspenseyCommit(
fiber,
committedLanes,
suspendedState,
);
}
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
if ((fiber.flags & suspenseyCommitFlag) !== NoFlags) {
const props: ViewTransitionProps = fiber.memoizedProps;
const name: ?(string | 'auto') = props.name;
if (name != null && name !== 'auto') {
// This is a named ViewTransition being mounted or reappearing. Let's add it to
// the map so we can match it with deletions later.
//
// 这是一个正在挂载或重新出现的命名 ViewTransition。让我们将它添加到地图
// 中,这样以后可以与删除操作匹配。
const state: ViewTransitionState = fiber.stateNode;
// Reset the pair in case we didn't end up restoring the instance in previous commits.
// This shouldn't really happen anymore but just in case. We could maybe add an invariant.
//
// 重置该对,以防我们在之前的提交中没有恢复实例。
// 这种情况现在应该不会再发生,但以防万一。我们或许可以添加一个不变量。
state.paired = null;
trackAppearingViewTransition(name, state);
}
}
recursivelyAccumulateSuspenseyCommit(
fiber,
committedLanes,
suspendedState,
);
break;
}
// Fallthrough
// 贯穿
}
default: {
recursivelyAccumulateSuspenseyCommit(
fiber,
committedLanes,
suspendedState,
);
}
}
}
44. 分离备用兄弟节点
function detachAlternateSiblings(parentFiber: Fiber) {
// A fiber was deleted from this parent fiber, but it's still part of the
// previous (alternate) parent fiber's list of children. Because children
// are a linked list, an earlier sibling that's still alive will be
// connected to the deleted fiber via its `alternate`:
//
// 一个 fiber 从这个父 fiber 中被删除了,但它仍然是先前(备用)父 fiber 子节点列表的
// 一部分。由于子节点是一个链表,一个仍然存在的早期兄弟节点将通过它的 `alternate`
// 连接到被删除的 fiber:
//
// live fiber --alternate--> previous live fiber --sibling--> deleted
// fiber
//
// 活动 fiber --替代--> 以前的活动 fiber -- 同级--> 已删除的 fiber
//
// We can't disconnect `alternate` on nodes that haven't been deleted yet,
// but we can disconnect the `sibling` and `child` pointers.
//
// 我们不能断开尚未被删除节点的 `alternate`,
// 但我们可以断开 `sibling` 和 `child` 指针。
const previousFiber = parentFiber.alternate;
if (previousFiber !== null) {
let detachedChild = previousFiber.child;
if (detachedChild !== null) {
previousFiber.child = null;
do {
const detachedSibling = detachedChild.sibling;
detachedChild.sibling = null;
detachedChild = detachedSibling;
} while (detachedChild !== null);
}
}
}
45. 递归遍历被动卸载副作用
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现logComponentUnmount()由 ReactFiberPerformanceTrack#logComponentUnmount 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现
function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects have fired.
//
// 删除效果可以安排在任何 fiber 类型上。它们需要在子效果触发之前发生。
const deletions = parentFiber.deletions;
if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
const prevEffectStart = pushComponentEffectStart();
// TODO: Convert this to use recursion
// 待办:将此使用递归转换
nextEffect = childToDelete;
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
childToDelete,
parentFiber,
);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(childToDelete.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentUnmount(
childToDelete,
componentEffectStartTime,
componentEffectEndTime,
);
}
popComponentEffectStart(prevEffectStart);
}
}
detachAlternateSiblings(parentFiber);
}
// TODO: Split PassiveMask into separate masks for mount and unmount?
// 待办:将 PassiveMask 拆分为安装和卸载的独立掩码?
if (parentFiber.subtreeFlags & PassiveMask) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveUnmountOnFiber(child);
child = child.sibling;
}
}
}
46. 在 Fiber 上提交被动卸载
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现commitHookPassiveUnmountEffects()由 ReactFiberCommitEffects#commitHookPassiveUnmountEffects 实现pushNestedEffectDurations()由 ReactProfilerTimer#pushNestedEffectDurations 实现popNestedEffectDurations()由 ReactProfilerTimer#popNestedEffectDurations 实现bubbleNestedEffectDurations()由 ReactProfilerTimer#bubbleNestedEffectDurations 实现logComponentDisappeared()由 ReactFiberPerformanceTrack#logComponentDisappeared 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现
function commitPassiveUnmountOnFiber(finishedWork: Fiber): void {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (finishedWork.flags & Passive) {
commitHookPassiveUnmountEffects(
finishedWork,
finishedWork.return,
HookPassive | HookHasEffect,
);
}
break;
}
case HostRoot: {
const prevProfilerEffectDuration = pushNestedEffectDurations();
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (enableProfilerTimer && enableProfilerCommitHooks) {
const finishedRoot: FiberRoot = finishedWork.stateNode;
finishedRoot.passiveEffectDuration += popNestedEffectDurations(
prevProfilerEffectDuration,
);
}
break;
}
case Profiler: {
const prevProfilerEffectDuration = pushNestedEffectDurations();
recursivelyTraversePassiveUnmountEffects(finishedWork);
if (enableProfilerTimer && enableProfilerCommitHooks) {
const profilerInstance = finishedWork.stateNode;
// Propagate layout effect durations to the next nearest Profiler ancestor.
// Do not reset these values until the next render so DevTools has a chance to
// read them first.
//
// 将布局效果持续时间传播到下一个最近的 Profiler 上层元素。
// 在下一次渲染之前不要重置这些值,以便开发者工具先有机会读取它们。
profilerInstance.passiveEffectDuration += bubbleNestedEffectDurations(
prevProfilerEffectDuration,
);
}
break;
}
case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
const nextState: OffscreenState | null = finishedWork.memoizedState;
const isHidden = nextState !== null;
if (
isHidden &&
instance._visibility & OffscreenPassiveEffectsConnected &&
// For backwards compatibility, don't unmount when a tree suspends. In
// the future we may change this to unmount after a delay.
//
// 为了向后兼容,当树挂起时不要卸载。将来我们可能会改为延迟卸载。
(finishedWork.return === null ||
finishedWork.return.tag !== SuspenseComponent)
) {
// The effects are currently connected. Disconnect them.
// TODO: Add option or heuristic to delay before disconnecting the
// effects. Then if the tree reappears before the delay has elapsed, we
// can skip toggling the effects entirely.
//
// 目前效果是连接的。断开它们。
// 待办事项:添加选项或启发式方法,在断开效果之前延迟。
// 如果树在延迟结束前重新出现,我们可以完全跳过切换效果。
instance._visibility &= ~OffscreenPassiveEffectsConnected;
recursivelyTraverseDisconnectPassiveEffects(finishedWork);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
logComponentDisappeared(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
);
}
} else {
recursivelyTraversePassiveUnmountEffects(finishedWork);
}
break;
}
default: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(finishedWork.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
(componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
) {
logComponentEffect(
finishedWork,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
popComponentEffectErrors(prevEffectErrors);
}
47. 递归遍历断开被动效果
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现logComponentUnmount()由 ReactFiberPerformanceTrack#logComponentUnmount 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现
function recursivelyTraverseDisconnectPassiveEffects(parentFiber: Fiber): void {
// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects have fired.
// 删除效果可以安排在任何 fiber 类型上。它们需要在子效果触发之前发生。
const deletions = parentFiber.deletions;
if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
const prevEffectStart = pushComponentEffectStart();
// TODO: Convert this to use recursion
// 待办:将此转换为使用递归
nextEffect = childToDelete;
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
childToDelete,
parentFiber,
);
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(childToDelete.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
componentEffectEndTime - componentEffectStartTime > 0.05
) {
// While this is inside the disconnect path. This is a deletion within the
// disconnected tree. We currently log this for deletions in the mutation
// phase since it's shared by the disappear path.
//
// 当这处于断开路径中时。这是在断开树内的删除操作。我们目前在变更阶段记录此类删除,因为
// 它与消失路径共享。
logComponentUnmount(
childToDelete,
componentEffectStartTime,
componentEffectEndTime,
);
}
popComponentEffectStart(prevEffectStart);
}
}
detachAlternateSiblings(parentFiber);
}
// TODO: Check PassiveStatic flag
// 待办事项:检查 PassiveStatic 标志
let child = parentFiber.child;
while (child !== null) {
disconnectPassiveEffect(child);
child = child.sibling;
}
}
48. 提交被动卸载已删除树内部的副作用(开始)
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
deletedSubtreeRoot: Fiber,
nearestMountedAncestor: Fiber | null,
) {
while (nextEffect !== null) {
const fiber = nextEffect;
// Deletion effects fire in parent -> child order
// TODO: Check if fiber has a PassiveStatic flag
//
// 删除按父 -> 子顺序触发
// TODO: 检查 fiber 是否有 PassiveStatic 标志
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
const child = fiber.child;
// TODO: Only traverse subtree if it has a PassiveStatic flag.
// TODO: 只有在子树具有 PassiveStatic 标记时才遍历它。
if (child !== null) {
child.return = fiber;
nextEffect = child;
} else {
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
deletedSubtreeRoot,
);
}
}
}
49. 已删除树内的被动卸载副作用(完成)
function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
deletedSubtreeRoot: Fiber,
) {
while (nextEffect !== null) {
const fiber = nextEffect;
const sibling = fiber.sibling;
const returnFiber = fiber.return;
// Recursively traverse the entire deleted tree and clean up fiber fields.
// This is more aggressive than ideal, and the long term goal is to only
// have to detach the deleted tree at the root.
//
// 递归遍历整个已删除的树并清理 fiber 字段。
// 这比理想情况更激进,长期目标是只需在根节点处分离已删除的树。
detachFiberAfterEffects(fiber);
if (fiber === deletedSubtreeRoot) {
nextEffect = null;
return;
}
if (sibling !== null) {
sibling.return = returnFiber;
nextEffect = sibling;
return;
}
nextEffect = returnFiber;
}
}
50. 在 Fiber 中提交被动卸载已删除的树
备注
pushComponentEffectStart()由 ReactProfilerTimer#pushComponentEffectStart 实现pushComponentEffectDuration()由 ReactProfilerTimer#pushComponentEffectDuration 实现pushComponentEffectErrors()由 ReactProfilerTimer#pushComponentEffectErrors 实现pushComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#pushComponentEffectDidSpawnUpdate 实现commitHookPassiveUnmountEffects()由 ReactFiberCommitEffects#commitHookPassiveUnmountEffects 实现retainCache()由 ReactFiberCacheComponent#retainCache 实现releaseCache()由 ReactFiberCacheComponent#releaseCache 实现logComponentEffect()由 ReactFiberPerformanceTrack#logComponentEffect 实现popComponentEffectStart()由 ReactProfilerTimer#popComponentEffectStart 实现popComponentEffectDuration()由 ReactProfilerTimer#popComponentEffectDuration 实现popComponentEffectErrors()由 ReactProfilerTimer#popComponentEffectErrors 实现popComponentEffectDidSpawnUpdate()由 ReactProfilerTimer#popComponentEffectDidSpawnUpdate 实现
function commitPassiveUnmountInsideDeletedTreeOnFiber(
current: Fiber,
nearestMountedAncestor: Fiber | null,
): void {
const prevEffectStart = pushComponentEffectStart();
const prevEffectDuration = pushComponentEffectDuration();
const prevEffectErrors = pushComponentEffectErrors();
const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
switch (current.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
commitHookPassiveUnmountEffects(
current,
nearestMountedAncestor,
HookPassive,
);
break;
}
// TODO: run passive unmount effects when unmounting a root.
// Because passive unmount effects are not currently run,
// the cache instance owned by the root will never be freed.
// When effects are run, the cache should be freed here:
//
// 待办事项:在卸载根节点时运行被动卸载效果。
// 因为当前不会运行被动卸载效果,根节点拥有的缓存实例将永远不会被释放。
// 当效果运行时,缓存应在此处被释放:
//
// case HostRoot: {
// const cache = current.memoizedState.cache;
// releaseCache(cache);
// break;
// }
//
case LegacyHiddenComponent:
case OffscreenComponent: {
if (
current.memoizedState !== null &&
current.memoizedState.cachePool !== null
) {
const cache: Cache = current.memoizedState.cachePool.pool;
// Retain/release the cache used for pending (suspended) nodes.
// Note that this is only reached in the non-suspended/visible case:
// when the content is suspended/hidden, the retain/release occurs
// via the parent Suspense component (see case above).
//
// 保留/释放用于挂起(暂停)节点的缓存。
// 请注意,这仅在未挂起/可见的情况下才会执行:
// 当内容被挂起/隐藏时,保留/释放操作是通过父 Suspense 组件进行的(见上面的情况)。
if (cache != null) {
retainCache(cache);
}
}
break;
}
case SuspenseComponent: {
if (enableTransitionTracing) {
// We need to mark this fiber's parents as deleted
// 我们需要将这个 fiber 的父节点标记为已删除
const offscreenFiber: Fiber = current.child as any;
const instance: OffscreenInstance = offscreenFiber.stateNode;
const transitions = instance._transitions;
if (transitions !== null) {
const abortReason: TransitionAbort = {
reason: 'suspense',
name: current.memoizedProps.name || null,
};
if (
current.memoizedState === null ||
current.memoizedState.dehydrated === null
) {
abortParentMarkerTransitionsForDeletedFiber(
offscreenFiber,
abortReason,
transitions,
instance,
true,
);
if (nearestMountedAncestor !== null) {
abortParentMarkerTransitionsForDeletedFiber(
nearestMountedAncestor,
abortReason,
transitions,
instance,
false,
);
}
}
}
}
break;
}
// 缓存组件
case CacheComponent: {
const cache = current.memoizedState.cache;
releaseCache(cache);
break;
}
// 追踪标记组件
case TracingMarkerComponent: {
if (enableTransitionTracing) {
// We need to mark this fiber's parents as deleted
// 我们需要将这个 fiber 的父节点标记为已删除
const instance: TracingMarkerInstance = current.stateNode;
const transitions = instance.transitions;
if (transitions !== null) {
const abortReason: TransitionAbort = {
reason: 'marker',
name: current.memoizedProps.name,
};
abortParentMarkerTransitionsForDeletedFiber(
current,
abortReason,
transitions,
null,
true,
);
if (nearestMountedAncestor !== null) {
abortParentMarkerTransitionsForDeletedFiber(
nearestMountedAncestor,
abortReason,
transitions,
null,
false,
);
}
}
}
break;
}
}
if (
enableProfilerTimer &&
enableProfilerCommitHooks &&
enableComponentPerformanceTrack &&
(current.mode & ProfileMode) !== NoMode &&
componentEffectStartTime >= 0 &&
componentEffectEndTime >= 0 &&
(componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
) {
logComponentEffect(
current,
componentEffectStartTime,
componentEffectEndTime,
componentEffectDuration,
componentEffectErrors,
);
}
popComponentEffectStart(prevEffectStart);
popComponentEffectDuration(prevEffectDuration);
popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
popComponentEffectErrors(prevEffectErrors);
}
51. 隐藏或显示Fiber上所有子节点
备注
commitShowHideHostInstance()由 ReactFiberCommitHostEffects#commitShowHideHostInstance 实现commitShowHideHostTextInstance()由 ReactFiberCommitHostEffects#commitShowHideHostTextInstance 实现commitShowHideSuspenseBoundary()由 ReactFiberCommitHostEffects#commitShowHideSuspenseBoundary 实现
function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) {
if (!supportsMutation) {
return;
}
switch (fiber.tag) {
case HostComponent:
case HostHoistable: {
// Found the nearest host component. Hide it.
// 找到最近的宿主组件。隐藏它。
commitShowHideHostInstance(fiber, isHidden);
// Typically, only the nearest host nodes need to be hidden, since that
// has the effect of also hiding everything inside of them.
// 通常,只需隐藏最近的主机节点,因为这样也能隐藏其中的所有内容。
//
// However, there's a special case for portals, because portals do not
// exist in the regular host tree hierarchy; we can't assume that just
// because a portal's HostComponent parent in the React tree will also be
// a parent in the actual host tree.
//
// 但是,对于门户有一个特殊情况,因为门户在常规宿主树层级中不存在;我们不能假设仅仅因
// 为门户在 React 树中的 HostComponent 父组件在实际宿主树中也是父组件。
//
// So, if any portals exist within the tree, regardless of how deeply
// nested they are, we need to repeat this algorithm for its children.
//
// 所以,如果树中存在任何传送门,无论它们嵌套得多深,我们都需要对它的子节点重复这个算法。
hideOrUnhideNearestPortals(fiber, isHidden);
return;
}
case HostText: {
commitShowHideHostTextInstance(fiber, isHidden);
return;
}
case DehydratedFragment: {
commitShowHideSuspenseBoundary(fiber, isHidden);
return;
}
case OffscreenComponent:
case LegacyHiddenComponent: {
const offscreenState: OffscreenState | null = fiber.memoizedState;
if (offscreenState !== null) {
// Found a nested Offscreen component that is hidden.
// Don't search any deeper. This tree should remain hidden.
// 发现一个嵌套的 Offscreen 组件被隐藏。不要再深入搜索。这个树应该保持隐藏状态。
} else {
hideOrUnhideAllChildren(fiber, isHidden);
}
return;
}
default: {
hideOrUnhideAllChildren(fiber, isHidden);
return;
}
}
}
52. 隐藏或显示最近的传送门
function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) {
if (!supportsMutation) {
return;
}
if (parentFiber.subtreeFlags & PortalStatic) {
let child = parentFiber.child;
while (child !== null) {
hideOrUnhideNearestPortalsOnFiber(child, isHidden);
child = child.sibling;
}
}
}
53. 隐藏或显示 Fiber 上最近的传送门
function hideOrUnhideNearestPortalsOnFiber(fiber: Fiber, isHidden: boolean) {
if (!supportsMutation) {
return;
}
switch (fiber.tag) {
case HostPortal: {
// Found a portal. Switch back to the normal hide/unhide algorithm to
// toggle the visibility of its children.
// 找到了一个传送门。切换回正常的显示/隐藏算法来,切换其子对象的可见性。
hideOrUnhideAllChildrenOnFiber(fiber, isHidden);
return;
}
case OffscreenComponent: {
const offscreenState: OffscreenState | null = fiber.memoizedState;
if (offscreenState !== null) {
// Found a nested Offscreen component that is hidden. Don't search any
// deeper. This tree should remain hidden.
// 发现了一个被隐藏的嵌套 Offscreen 组件。不要再深入搜索。这棵树应该保持隐藏状态。
} else {
hideOrUnhideNearestPortals(fiber, isHidden);
}
return;
}
default: {
hideOrUnhideNearestPortals(fiber, isHidden);
return;
}
}
}