一、Before Mutation
1、职责
Before Mutation阶段的主要职责是:执行在DOM变更之前的可能需要处理的副作用操作;
- 对于函数组件而言,就是
useEffect注入的回调函数; - 对于类组件而言,就是调用
getSnapshotBeforeUpdate生命周期方法。
2、各个函数的作用
(1)commitBeforeMutationEffects函数
该函数是Before Mutation阶段的入口函数,该函数会调用commitBeforeMutationEffects_begin函数遍历Fiber树。
(2)commitBeforeMutationEffects_begin函数
该函数的主要作用是:遍历整个Fiber树,找到需要在DOM变更之前处理的副作用逻辑;
该函数向下遍历,对每个节点进行遍历,检查每个节点的subtreeFlags是否包含BeforeMutationMask标志,如果包含该标志,就说明当前节点或其子节点可能存在在DOM变更之前需要处理的副作用。
如果当前Fiber节点有子节点,并且包含BeforeMutationMask标志,就会继续遍历其子节点,确保在进行DOM突变前所有必要的副作用都被正确处理;
(3)commitBeforeMutationEffects_complete函数
该函数的主要职责:负责处理Fiber树中的副作用逻辑,会向上回溯到父节点,遍历完整个Fiber树,确保所有必要的副作用被正确执行。
该函数会调用commitBeforeMutationEffectsOnFiber函数执行副作用逻辑。
(4)commitBeforeMutationEffectsOnFiber函数
该函数负责对Fiber节点上的某些副作用进行处理;根据Fiber节点的tag属性对不同类型的节点进行处理,在进行处理时,会通过Fiber树中的finishedWork节点的flags标记位来决定是否需要执行特定的副作用;
- 针对函数组件:(flags & Update) !== NoFlags 通过此标记判断是否需要执行副作用;
- 如果启用了useEffect事件钩子且存在更新,则处理useEffect相关的副作用;
- 针对类组件:(flags & Snapshot) !== NoFlags 通过此标记判断是否需要执行副作用;
- 处理getSnapshotBeforeUpdate生命周期钩子,并保存快照值以供后续的componentDidUpdate使用
- 针对宿主根节点:(flags & Snapshot) !== NoFlags 通过此标记判断是否需要执行副作用;
- 对于宿主根节点,处理与容器清理相关的操作;
- 如果某些不应该产生副作用的节点出现了副作用标记,则抛出错误;
(5)commitUseEffectEventMount函数
该函数的主要职责是:处理和挂载与useEffect相关的事件处理函数;确保在useEffect中定义的事件处理函数被挂载到正确的引用对象ref上;
【应用场景】 当你在函数组件中使用useEffect来设置事件监听器时,React会在渲染完成后调用这个commitUseEffectEventMount函数,将事件处理程序挂载到指定的DOM元素或对象上;
二、源码解析
1、commitBeforeMutationEffects函数
/************ before mutation阶段 ************/
let nextEffect: Fiber | null = null; // 指向当前需要处理的Fiber节点
export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
): boolean {
nextEffect = firstChild; // 获取当前Fiber节点
commitBeforeMutationEffects_begin();
// 省略了一些代码,只保留核心代码
}
2、commitBeforeMutationEffects_begin函数
function commitBeforeMutationEffects_begin() {
// nextEffect:指向当前需要处理的Fiber节点
// while循环遍历整个Fiber树的各个节点。当nextEffect为null时,表示所需要处理的节点已经遍历完毕
while (nextEffect !== null) { // 当前Fiber节点不为null
const fiber = nextEffect;
const child = fiber.child; // 获取当前Fiber节点的第一个子节点
// 检查当前Fiber节点的subtreeFlags是否包含BeforeMutationMask标志
// 表示当前节点或其子节点可能存在在DOM变更之前需要处理的副作用
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
child.return = fiber; // return属性用于指向当前节点的父节点,帮助形成父子关系
// 将 nextEffect 设置为当前节点的第一个子节点 child,在下一次循环中处理
nextEffect = child;
} else {
// 执行副作用逻辑
commitBeforeMutationEffects_complete();
}
}
}
3、commitBeforeMutationEffects_complete函数
function commitBeforeMutationEffects_complete() {
// 当 nextEffect 为 null 时,表示所有需要处理的 Fiber 节点都已经遍历完毕,循环终止。
while (nextEffect !== null) {
const fiber = nextEffect; // 当前要处理的fiber节点
try {
// 执行实际的副作用
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
// 错误捕获
captureCommitPhaseError(fiber, fiber.return, error);
}
// 获取兄弟节点
const sibling = fiber.sibling; // 获取当前Fiber节点的兄弟节点
if (sibling !== null) { // 有兄弟节点
// 将兄弟节点的父节点设置为当前Fiber节点的父节点,用于维护父子关系,确保可以访问到正确的父节点
sibling.return = fiber.return;
// 将nextEffect设置为当前节点的兄弟节点,以便在下一次循环中处理这个兄弟节点;
nextEffect = sibling;
// 函数立即返回,继续处理兄弟节点;
return;
}
// 否则,向上,返回到父节点,继续处理父节点的兄弟节点或其他逻辑。
nextEffect = fiber.return;
}
}
4、commitBeforeMutationEffectsOnFiber函数
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate; // 获取当前Fiber节点对应的旧的Fiber节点,即上次渲染时的Fiber节点;如果是首次渲染,current为null
const flags = finishedWork.flags; // 获取当前Fiber节点上的标记位,用来判断当前节点是否有特定类型的副作用需要处理,
switch (finishedWork.tag) { // 根据Fiber节点的tag对不同类型的节点进行处理
case FunctionComponent: { // 如果是函数组件
if (enableUseEffectEventHook) { // 检查是否启用了useEffect事件钩子
if ((flags & Update) !== NoFlags) { // 判断当前节点是否有Update标记,如果有,则表示有副作用需要处理
// 处理useEffect钩子中注册的事件回调
commitUseEffectEventMount(finishedWork); // 该操作在DOM突变之前(即渲染到屏幕前)执行
}
}
break;
}
case ClassComponent: { // 处理类组件
if ((flags & Snapshot) !== NoFlags) { // 判断当前组件是否有SnapShot标记,如果有,则表示需要执行getSnapshotBeforeUpdate生命周期钩子
if (current !== null) { // 如果不是首次渲染
const prevProps = current.memoizedProps; // 获取上次渲染的props
const prevState = current.memoizedState; // 获取上次渲染的state
const instance = finishedWork.stateNode; // 获取类组件实例,即真实DOM节点
// 类组件:执行getSnapshotBeforeUpdate生命周期函数,并传入上次渲染的props和state
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
// 这个值将在组件更新完成后被 componentDidUpdate 使用。
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
}
break;
}
case HostRoot: { // 如果是宿主根节点,即React应用的根节点
if ((flags & Snapshot) !== NoFlags) { // 检查根节点是否有Snapshot标记
if (supportsMutation) { // 检查当前是否是浏览器环境,即是否支持DOM突变
const root = finishedWork.stateNode;
clearContainer(root.containerInfo); // 在DOM环境中,清空根节点的容器内容
}
}
break;
}
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、commitUseEffectEventMount函数
// finishedWork:表示React渲染过程中已经处理完成的节点
function commitUseEffectEventMount(finishedWork: Fiber) {
// updateQueue:一个队列,存储着在useEffect等钩子中产生的更新任务;
// 在Fiber节点上,updateQueue用于管理与副作用相关的工作,对于函数组件,updateQueue中会存储副作用和事件等相关信息
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any);
// 获取事件相关的负载数据,events属性包含了useEffect钩子中注册的事件处理函数及其关联的ref;
const eventPayloads = updateQueue !== null ? updateQueue.events : null;
if (eventPayloads !== null) { // 表示有事件挂载
for (let ii = 0; ii < eventPayloads.length; ii++) {
// ref:事件引用对象,用于在组件中引用DOM元素或自定义事件
// nextImpl: 下一个事件处理函数,即:useEffect中定义的事件处理逻辑
const { ref, nextImpl } = eventPayloads[ii];
// 将事件处理函数挂载到ref.imp上
// 这里实际上是为引用的DOM元素或自定义对象绑定useEffect钩子中的事件处理逻辑
ref.impl = nextImpl;
}
}
}