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

110 阅读6分钟

一、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; 
        } 
    } 
}