Appearance
Before Mutation Effects 阶段
在这个阶段,React主要做了以下的事情:
- 捕获 DOM 快照: 最典型的用例是类组件的
getSnapshotBeforeUpdate()
生命周期方法。它允许组件在 DOM 更新前捕获当前 DOM 的某些状态(例如滚动位置、元素大小等)。 - 执行卸载相关的清理 (部分): 虽然组件的完全卸载横跨多个子阶段,但
componentWillUnmount
的调用也发生在这个“突变前”的窗口期,确保在组件的 DOM 节点被移除前执行清理逻辑。
虽然 React 更推崇使用 Hooks,但 getSnapshotBeforeUpdate
仍然是处理特定场景(如维护滚动位置或焦点)的有效工具,尤其是在与第三方库或旧有代码集成时。
对于函数组件,虽然没有直接等同于 getSnapshotBeforeUpdate
的 Hook 在“突变前”执行任意代码,但 useLayoutEffect
的清理函数(如果组件更新或卸载)会在 DOM 突变后、浏览器绘制前同步执行,而其创建函数也是同步的。不过,严格意义上的“突变前读取”主要还是 getSnapshotBeforeUpdate
的领域。
函数调用链路图
函数源码解析
commitBeforeMutationEffects
commitBeforeMutationEffects
javascript
// ... existing code ...
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes,
): void {
focusedInstanceHandle = prepareForCommit(root.containerInfo); // 准备提交,例如在RN中获取焦点信息
shouldFireAfterActiveInstanceBlur = false;
const isViewTransitionEligible =
enableViewTransition &&
includesOnlyViewTransitionEligibleLanes(committedLanes);
nextEffect = firstChild; // 从第一个子节点开始遍历
commitBeforeMutationEffects_begin(isViewTransitionEligible);
focusedInstanceHandle = null;
resetAppearingViewTransitions();
}
function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {
const subtreeMask = isViewTransitionEligible
? BeforeAndAfterMutationTransitionMask // 包含 Snapshot 和 ViewTransition 相关的 flags
: BeforeMutationMask; // 通常只包含 Snapshot flag
while (nextEffect !== null) {
const fiber = nextEffect;
const child = fiber.child;
// 检查当前 Fiber 的 subtreeFlags 是否包含需要处理的副作用
if ((fiber.subtreeFlags & subtreeMask) !== NoFlags && child !== null) {
// ... 确保父节点存在 ...
child.return = fiber;
nextEffect = child; // 向下遍历
} else {
commitBeforeMutationEffects_complete(isViewTransitionEligible);
}
}
}
function commitBeforeMutationEffects_complete(isViewTransitionEligible: boolean) {
while (nextEffect !== null) {
const fiber = nextEffect;
// 实际处理当前 Fiber 的 BeforeMutation Effects
commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);
const sibling = fiber.sibling;
if (sibling !== null) {
// ... 确保父节点存在 ...
sibling.return = fiber.return;
nextEffect = sibling; // 处理兄弟节点
return;
}
}
}
// 真正执行单个 Fiber 的 BeforeMutation Effect 的函数
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber, isViewTransitionEligible: boolean) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if ((flags & Snapshot) !== NoFlags) { // 检查 Snapshot 标记
switch (finishedWork.tag) {
case ClassComponent:
if (current !== null) {
// 调用 getSnapshotBeforeUpdate
commitClassSnapshot(current, finishedWork);
}
break;
case HostRoot:
// ... HostRoot 的 Snapshot 处理 (例如 Hydration) ...
break;
// ... 其他类型的 Fiber (HostComponent, HostText 等通常不处理 Snapshot) ...
}
}
if (enableViewTransition && isViewTransitionEligible) {
if ((flags & ViewTransitionNamedStatic) !== NoFlags) {
// 视图过渡相关的处理
commitBeforeUpdateViewTransition(finishedWork);
}
}
// ...
}
// ... existing code ...
这个函数是 React “变更前副作用”(Before Mutation Effects)阶段的起点。它的主要工作流程是:
准备工作:
- 调用
prepareForCommit
来获取当前宿主环境的一些状态(比如浏览器中当前聚焦的元素),并存储在focusedInstanceHandle
中。 - 判断当前提交是否适用于“视图过渡”(View Transition)特性,结果保存在
isViewTransitionEligible
。
- 调用
启动遍历:
- 将全局变量
nextEffect
指向firstChild
,即副作用链表中的第一个 Fiber 节点。 - 调用
commitBeforeMutationEffects_begin(isViewTransitionEligible)
来开始遍历和处理副作用链表。这个_begin
函数会进行深度优先遍历,并在遍历过程中,当一个节点的子树处理完毕后,会调用commitBeforeMutationEffects_complete
,而_complete
函数内部则会调用commitBeforeMutationEffectsOnFiber
来处理当前节点。
- 将全局变量
收尾工作:
- 在遍历完成后,重置
focusedInstanceHandle
和视图过渡相关的状态。
- 在遍历完成后,重置
简单来说,commitBeforeMutationEffects
设置好初始状态,然后通过调用内部的辅助函数(如 commitBeforeMutationEffects_begin
和 commitBeforeMutationEffects_complete
)来驱动一个深度优先的副作用处理流程。在这个流程中,commitBeforeMutationEffectsOnFiber
会被适时调用。
commitBeforeMutationEffectsOnFiber
当遍历到副作用链表中的某个具体 Fiber 节点 (finishedWork
) 时,此函数会被调用来执行该节点在“变更前”阶段需要处理的特定副作用。
主要逻辑是根据 finishedWork.tag
(Fiber 类型) 和 finishedWork.flags
(副作用标记) 进行 switch
判断:
对于
ClassComponent
:- 如果该 Fiber 带有
Snapshot
标记 (通常意味着组件定义了getSnapshotBeforeUpdate
生命周期方法并且需要执行它):- 会调用
commitClassSnapshot(finishedWork, current)
。这个函数内部会获取组件实例,并调用其实例的getSnapshotBeforeUpdate
方法,将返回的快照值存储起来,供后续的componentDidUpdate
使用。
- 会调用
- 如果该 Fiber 带有
对于
HostRoot
(根节点):- 如果根 Fiber 带有
Snapshot
标记 (比较少见的情况,例如整个应用因key
变化而重新挂载):- 并且当前环境支持 DOM 操作 (
supportsMutation
为 true),则会调用clearContainer(root.containerInfo)
来清空根 DOM 容器的内容。
- 并且当前环境支持 DOM 操作 (
- 如果根 Fiber 带有
对于
FunctionComponent
(当enableUseEffectEventHook
特性开启时):- 如果 Fiber 有
Update
标记,并且其updateQueue
中有useEffectEvent
产生的事件回调,则会更新这些事件回调的实现,确保它们是最新的。
- 如果 Fiber 有
对于
ViewTransitionComponent
(当enableViewTransition
特性开启时):- 如果符合视图过渡条件,并且是更新操作 (
current !== null
),会调用commitBeforeUpdateViewTransition
来为即将发生的视图过渡做准备,比如记录旧的视图名称。
- 如果符合视图过渡条件,并且是更新操作 (
其他类型的 Fiber (如
HostComponent
,HostText
等) 在此阶段通常没有特别的Snapshot
副作用需要处理。如果它们错误地带上了Snapshot
标记,会抛出错误。
此外,在 switch
之前,还会处理与焦点管理相关的逻辑:
- 如果之前记录的
focusedInstanceHandle
(当前聚焦的元素) 位于一个即将被隐藏的SuspenseComponent
内部,会设置shouldFireAfterActiveInstanceBlur = true
并调用beforeActiveInstanceBlur
,为后续在 Mutation 阶段结束后处理失焦事件做准备。
总而言之,commitBeforeMutationEffects
负责驱动整个“变更前副作用”的遍历,而 commitBeforeMutationEffectsOnFiber
是实际执行单个 Fiber 节点特定副作用(主要是 getSnapshotBeforeUpdate
的调用、焦点处理和视图过渡准备)的核心函数。
