一、Mutation阶段
1、职责
在Mutation阶段,React会将虚拟DOM应用到真实DOM中;会针对不同类型的节点进行不同的处理。
2、各个函数的作用
(1)commitMutationEffects函数
该函数是在进入Mutation阶段时实际调用的函数,它会调用commitMutationEffectsOnFiber函数处理Fiber节点。
(2)commitMutationEffectsOnFiber函数
该函数是Mutation阶段开始处理Fiber节点的入口函数,该函数会对每个Fiber节点递归遍历子节点的变更副作用,除此之外,会根据Fiber节点的tag属性针对不同的节点类型做不同的处理:
- 针对函数组件:如果有Update标记,说明该Fiber节点需要更新,则会处理
useEffect或useLayoutEffect等副作用操作; - 针对类组件:如果有Ref标记,说明ref发生了变化,则需要先安全的移除旧的ref;
- 针对原生DOM节点:
- 如果有
Ref标记,说明ref发生了变化,则需要先安全的移除旧的ref; - 如果
supportsMutation为true,表示支持DOM变更,即为浏览器环境,则:- 如果有
ContentReset标志,说明需要重置文本内容,则调用该函数resetTextContent重置文本内容; - 如果有
Update标志,说明DOM节点的属性或内容有更新,则调用该函数commitUpdate处理更新;
- 如果有
- 如果有
- 针对文本节点:如果有
Update标志,说明文本内容有更新,则调用该函数commitTextUpdate将旧文本替换为新文本;
(3)recursivelyTraverseMutationEffects函数
该函数的主要作用是:递归遍历Fiber节点及其子节点,并处理其中的删除操作,主要是在真实DOM更新之前进行必要的清理操作。
(4)commitReconciliationEffects函数
该函数的主要作用是:对Fiber节点执行插入或排序操作,真正的插入或排序操作会调用commitPlacement函数。
二、源码解析
1、commitMutationEffects函数
/************ mutation阶段 ************/
export function commitMutationEffects(
root: FiberRoot,
finishedWork: Fiber,
committedLanes: Lanes,
) {
commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
}
2、commitMutationEffectsOnFiber函数
该函数负责对不同类型的Fiber节点执行必要的副作用处理,包括卸载/挂载useEffect 或 useLayoutEffect、处理ref、更新DOM属性、重置内容等。
function commitMutationEffectsOnFiber(
finishedWork: Fiber, // 当前要处理的Fiber节点
root: FiberRoot, // Fiber树的根节点
lanes: Lanes // 渲染优先级
) {
const current = finishedWork.alternate; // 获取上次渲染的Fiber节点
const flags = finishedWork.flags; // 获取当前Fiber节点的标记,包含需要执行的副作用类型
switch (finishedWork.tag) { // 根据tag属性来判断Fiber节点的类型
case FunctionComponent: { // 递归遍历子节点的变更副作用
recursivelyTraverseMutationEffects(root, finishedWork, lanes); // 处理该Fiber节点的调和效果,如:删除、插入、移动子节点等;
commitReconciliationEffects(finisheWork);
if (flags & Update) { // Fiber节点包含Update标记,表示该Fiber节点需要更新
try {
// 先执行钩子中的清理函数
commitHookEffectListUnmount(
HookInsertion | HookHasEffect, // 标记需要处理的钩子类型
finishedWork,
finishedWork.return,
);
// 执行钩子中注册的副作用函数
commitHookEffectListMount(
HookInsertion | HookHasEffect, // 标记需要处理的钩子类型
finishedWork
);
} catch (error) {
// 捕获执行副作用时的错误,并处理
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
try {
// 卸载旧的布局效果:布局效果必须在变更DOM前卸载,防止旧的副作用影响新的布局
commitHookEffectListUnmount(
HookLayout | HookHasEffect, // 标记处理useLayoutEffect相关的钩子
finishedWork,
finishedWork.return,
);
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
return; // 函数组件的副作用处理完毕
}
case ClassComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Ref) { // 类组件带有Ref标记,表示其ref属性发生了变更,需要先安全地移除旧的ref,以确保不引用旧的DOM元素
if (current !== null) {
safelyDetachRef(current, current.return);
}
}
// 类组件有Callback标记,且子树处于隐藏状态(如Suspense场景下),则将需要执行的回调延迟,避免无意义的渲染操作
if (flags & Callback && offscreenSubtreeIsHidden) {
const updateQueue: UpdateQueue<mixed> | null =
(finishedWork.updateQueue: any);
if (updateQueue !== null) {
deferHiddenCallbacks(updateQueue);
}
}
return;
}
case HostComponent: { // 处理原生DOM节点:div span
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Ref) { // 如果存在Ref标记,则先安全移除旧的ref
if (current !== null) {
safelyDetachRef(current, current.return);
}
}
if (supportsMutation) { // 支持DOM变更,通常是指浏览器环境下
if (finishedWork.flags & ContentReset) { // 如果存在ContentReset标记,则重置文本内容,用于清空某些DOM节点的内容
const instance: Instance = finishedWork.stateNode;
try {
resetTextContent(instance); // 重置DOM的文本内容
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
if (flags & Update) { // 包含Update标记,说明DOM节点的属性或内容发生了变化
const instance: Instance = finishedWork.stateNode;
if (instance !== null) {
const newProps = finishedWork.memoizedProps;
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
const updatePayload: null | UpdatePayload =
(finishedWork.updateQueue: any);
finishedFWork.updateQueue = null;
if (updatePayload !== null || diffInCommitPhase) {
try {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
} catch (error) {
captureCommitPhaseError(
finishedWork,
finishedWork.return,
error
);
}
}
}
}
}
return;
}
case HostText: { // 处理文本节点
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) { // 包含Update标记,说明文本内容有更新
if (supportsMutation) {
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 textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
const oldText: string = current !== null ? current.memoizedProps : newText;
try {
commitTextUpdate(textInstance, oldText, newText); // 将旧文本替换为新文本
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
}
return;
}
default: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
return;
}
}
}
3、recursivelyTraverseMutationEffects函数
function recursivelyTraverseMutationEffects(
root: FiberRoot, // fiber树的根节点,即整个React应用的根节点
parentFiber: Fiber, // 当前正在处理的父Fiber节点
lanes: Lanes, // 更新优先级,决定哪些任务优先处理
) {
// 在进行真实DOM的更新和插入前,要先进行删除操作,防止产生混乱
// 删除副作用可以在任何Fiber类型被调度;在子级副作用之前发生
const deletions = parentFiber.deletions; // 获取在render阶段收集好的deletions
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
try {
// 执行实际的删除操作:该函数会从真实DOM中删除该Fiber节点及其相关的副作用
commitDeletionEffects(root, parentFiber, childToDelete);
} catch (error) {
captureCommitPhaseError(childToDelete, parentFiber, error);
}
}
}
// 如果删除后,还有子节点,调用commitMutationEffectsOnFiber继续处理删除操作
if (parentFiber.subtreeFlags & MutationMask) {
let child = parentFiber.child;
while (child !== null) {
commitMutationEffectsOnFiber(child, root, lanes);
child = child.sibling;
}
}
}
4、commitReconciliationEffects函数
function commitReconciliationEffects(finishedWork: Fiber) {
// Placement效果可以调度在任何类型的Fiber节点上,但是它发生在其子节点的效果处理完成之后,自己本身的效果处理完成之前
const flags = finishedWork.flags; // 获取节点上需要处理的副作用类型:Placement、Update、Deletion
if (flags & Placement) { // 包含Placement标记,说明该节点需要被插入或重新排序
try {
commitPlacement(finishedWork); // 执行插入或排序操作
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
// 清除Placement标记,确保该节点已经被正确插入,并且在生命周期函数中可以正确调用,防止在后续阶段再次执行该操作
finishedWork.flags &= ~Placement;
}
}
5、副作用函数的处理
(1)commitHookEffectListUnmount函数
该函数的主要作用是处理函数组件 Hook 副作用的卸载,当组件卸载时,如果使用了useEffect 与 useLayoutEffect钩子函数,并且返回清理函数destroy,则会调用该函数执行清理操作。
// 执行effect的清除操作
function commitHookEffectListUnmount(
flags: HookFlags, // 表示要处理的副作用类型
finishedWork: Fiber, // 当前Fiber节点,表示已经完成渲染的Fiber节点,包含组件的状态和副作用队列
nearestMountedAncestor: Fiber | null, // 最近的挂载祖先 Fiber 节点,用于在错误处理时查找最近的已挂载组件。
) {
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any); // 获取副作用的更新队列,如果组件中使用了Hook钩子,副作用就会存储在该队列中
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next; // 获取链表中的第一个副作用
let effect = firstEffect;
do {
/* 作用:循环遍历副作用链表中的每个 effect 节点,
使用位运算 effect.tag & flags 来检查当前 effect 是否匹配传入的 flags。
如果 effect.tag 中包含需要处理的副作用类型(如 HookLayout 或 HookHasEffect),则继续执行卸载逻辑。
*/
if ((effect.tag & flags) === flags) {
// Unmount
const inst = effect.inst;
const destroy = inst.destroy; // destroy表示副作用清理函数
if (destroy !== undefined) {
inst.destroy = undefined; // 清理完后,清空该函数:防止重复执行
// 调用清理函数
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
// 清除函数的执行
function safelyCallDestroy(
current: Fiber,
nearestMountedAncestor: Fiber | null,
destroy: () => void,
) {
try {
destroy();
} catch (error) {
captureCommitPhaseError(current, nearestMountedAncestor, error);
}
}
(2)commitHookEffectListMount函数
该函数的主要作用是处理函数组件 Hook 副作用的挂载,即执行传入useEffect 与 useLayoutEffect 钩子的函数。
function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
const updateQueue: FunctionComponentUpdateQueue | null =
(finishedWork.updateQueue: any); // 读取副作用链表:该链表是一个循环链表
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) { // 存在副作用
const firstEffect = lastEffect.next; // 获取第一个副作用节点
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Mount
const create = effect.create; // 获取副作用函数
const inst = effect.inst;
const destroy = create(); // 执行副作用函数,并存储其返回值
inst.destroy = destroy; // 返回值不为空,则表示有副作用清理函数
}
effect = effect.next;
} while (effect !== firstEffect);
}
}