从React源码学习React的工作原理之渲染更新——Commit阶段之Mutation(八)

131 阅读7分钟

一、Mutation阶段

1、职责

        在Mutation阶段,React会将虚拟DOM应用到真实DOM中;会针对不同类型的节点进行不同的处理。

2、各个函数的作用

(1)commitMutationEffects函数
        该函数是在进入Mutation阶段时实际调用的函数,它会调用commitMutationEffectsOnFiber函数处理Fiber节点。

(2)commitMutationEffectsOnFiber函数
        该函数是Mutation阶段开始处理Fiber节点的入口函数,该函数会对每个Fiber节点递归遍历子节点的变更副作用,除此之外,会根据Fiber节点的tag属性针对不同的节点类型做不同的处理:

  • 针对函数组件:如果有Update标记,说明该Fiber节点需要更新,则会处理useEffectuseLayoutEffect等副作用操作;
  • 针对类组件:如果有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节点执行必要的副作用处理,包括卸载/挂载useEffectuseLayoutEffect、处理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 副作用的卸载,当组件卸载时,如果使用了useEffectuseLayoutEffect钩子函数,并且返回清理函数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); 
    } 
}