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

162 阅读6分钟

前情

        我们已经了解到,render阶段的主要职责是:通过diff算法计算出更新结果,并构建出一棵新的Fiber树,为commit阶段的渲染做准备;render阶段结束,就表明Fiber树的构建已经结束,接下来,就进入commit阶段,整个commit阶段主要是针对root上收集到的effectList进行处理。
        commit阶段的入口函数是commitRoot,函数中会调用commitBeforeMutationEffectscommitMutationEffectscommitLayoutEffects这三个函数,表示commit阶段的三个子阶段。

一、commit阶段

1、职责

        React的commit阶段主要负责将在render阶段计算得到的更新应用到页面上,并同步到真实DOM中;该阶段是React更新流程中的最后一个阶段,也是最重要的一个阶段,因为它直接决定了用户最终看到的页面内容;
        需要注意的是:commit阶段的任务是不可中断的;commit阶段的调度采用的是最高优先级,以保证commit阶段的同步执行不被打断;

2、执行过程

commit阶段又分为三个子阶段,分别如下:
(1)before mutation阶段
        该阶段通过执行commitBeforeMutationEffects函数实现;在该阶段,针对不同的组件类型,会执行不同的操作:

  • 类组件:调用getSnapshotBeforeUpdate生命周期函数;
  • 函数组件:异步调度useEffect中注册的回调函数; 该阶段主要是为接下来的 Mutation 阶段做准备。

(2)mutation阶段
        该阶段通过执行commitMutationEffects函数实现;在该阶段,会重置文本节点以及真实DOM节点的插入、删除、更新等操作;

  • HostComponent:进行相应的DOM操作;
  • 类组件:执行相应的DOM操作,还会在组件卸载或更新时,清除组件上已绑定的ref,以确保不再引用已卸载的DOM元素或组件实例;
  • 函数组件:先执行一次useEffect的清理函数,再执行useEffect传入的回调函数;

(3)layout阶段
        该阶段通过执行commitLayoutEffects函数实现;该阶段主要负责处理与组件生命周期相关的副作用以及layout类副作用(例如 useLayoutEffect 钩子),通常会涉及到DOM操作后的组件状态更新和副作用执行。

  • 类组件:调用组件的生命周期componentDidMountcomponentDidUpdate,调用setState的回调;
  • 函数组件:填充useEffect执行数组;

        该阶段发生在DOM突变后,浏览器绘制之前;该阶段允许组件读取最新的布局信息,并在浏览器将新布局绘制到屏幕之前进行额外的DOM操作;

二、源码分析

1、commitRootImpl

        commitRootImplcommit阶段的入口函数,该函数是React在更新过程中commit阶段的一个核心函数。在该阶段,React 会已完成的工作应用到真实的 DOM 上。

function commitRootImpl(
    root: FiberRoot,
    recoverableErrors: null | Array<CapturedValue<mixed>>,
    transitions: Array<Transition> | null,
    renderPriorityLevel: EventPriority,
) {
    // 获取已经构建完成的Fiber树及其优先级 
    const finishedWork = root.finishedWork;
    const lanes = root.finishedLanes;

    // finishedWork为空,则停止commit
    if (finishedWork === null) {
        return null;
    } else {

    }
    root.finishedWork = null;
    root.finishedLanes = NoLanes;

    if (finishedWork === root.current) {
        throw new Error(
            'Cannot commit the same tree as before. This error is likely caused by ' +
            'a bug in React. Please file an issue.',
        );
    }

    // commit阶段是同步执行的
    // 清除root上的信息,以便开启一次新的调度;
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    root.cancelPendingCommit = null;
    
    let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);

    const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
    remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);

    markRootFinished(root, remainingLanes);

    if (root === workInProgressRoot) {
        // We can reset these now that they are finished.
        workInProgressRoot = null;
        workInProgress = null;
        workInProgressRootRenderLanes = NoLanes;
    } else {
    }

    // 检查整棵树上是否有副作用
    const subtreeHasEffects =
        (finishedWork.subtreeFlags &
            (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
        NoFlags;
    const rootHasEffect =
        (finishedWork.flags &
            (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
        NoFlags;

    if (subtreeHasEffects || rootHasEffect) {
        // 有副作用,则执行副作用 
        const prevTransition = ReactCurrentBatchConfig.transition;
        ReactCurrentBatchConfig.transition = null;
        const previousPriority = getCurrentUpdatePriority();
        setCurrentUpdatePriority(DiscreteEventPriority);

        ReactCurrentOwner.current = null;

        // commitBeforeMutationEffects:Before Mutation 阶段
        const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
            root,
            finishedWork,
        );

        // Mutation 阶段 
        commitMutationEffects(root, finishedWork, lanes);

        root.current = finishedWork;
        // Layout 阶段
        commitLayoutEffects(finishedWork, root, lanes);
        setCurrentUpdatePriority(previousPriority);
    } else {
        // No effects.
        // 没有副作用,直接替换构建好的树
        root.current = finishedWork;
    }
  }
  // 省略...
  return null;
}

2、commitBeforeMutationEffects

export function commitBeforeMutationEffects(
    root: FiberRoot,
    firstChild: Fiber,
): boolean {
    nextEffect = firstChild; 
    // 开启Fiber树的遍历
    commitBeforeMutationEffects_begin();

    // We no longer need to track the active instance fiber
    const shouldFire = shouldFireAfterActiveInstanceBlur;
    shouldFireAfterActiveInstanceBlur = false;
    focusedInstanceHandle = null;

    return shouldFire; // shouldFire表示是否在后续的 DOM 变更后触发相关事件。
}

function commitBeforeMutationEffects_begin() {
    while (nextEffect !== null) {
        const fiber = nextEffect;

        // 获取当前fiber对象的子节点
        const child = fiber.child;
        if (
            (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
            child !== null
        ) {
            // 在DOM变更前有副作用需要处理,并且子节点不为null,则处理子节点 
            child.return = fiber;
            nextEffect = child;
        } else {
            // 如果当前Fiber节点没有子节点或者在DOM变更前无副作用需要处理,则开始处理当前阶段或兄弟节点
            commitBeforeMutationEffects_complete();
        }
    }
}

function commitBeforeMutationEffects_complete() {
    while (nextEffect !== null) {
        const fiber = nextEffect;
        try {
            // 处理当前Fiber节点的副作用
            commitBeforeMutationEffectsOnFiber(fiber);
        } catch (error) {
            captureCommitPhaseError(fiber, fiber.return, error);
        }

        // 获取兄弟节点
        const sibling = fiber.sibling;
        if (sibling !== null) {
            sibling.return = fiber.return;
            // 有兄弟节点,则处理兄弟节点
            nextEffect = sibling;
            return;
        }

        // 否则,向上,返回到父节点,处理父节点
        nextEffect = fiber.return;
    }
}

function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
    const current = finishedWork.alternate;
    const flags = finishedWork.flags;

    switch (finishedWork.tag) {
        // 针对函数组件
        case FunctionComponent: {
            if (enableUseEffectEventHook) {
                if ((flags & Update) !== NoFlags) {
                    // 处理useEffect的副作用
                    commitUseEffectEventMount(finishedWork);
                }
            }
            break;
        }
        // 针对类组件
        case ClassComponent: {
            if ((flags & Snapshot) !== NoFlags) {
                if (current !== null) {
                    const prevProps = current.memoizedProps;
                    const prevState = current.memoizedState;
                    const instance = finishedWork.stateNode;
                    
                    // 类组件:执行getSnapshotBeforeUpdate生命周期函数
                    const snapshot = instance.getSnapshotBeforeUpdate(
                        finishedWork.elementType === finishedWork.type
                            ? prevProps
                            : resolveDefaultProps(finishedWork.type, prevProps),
                        prevState,
                    );
                    // 用来存储getSnapshotBeforeUpdate生命周期函数返回的快照数据,方便下次更新时使用
                    instance.__reactInternalSnapshotBeforeUpdate = snapshot;
                }
            }
            break;
        }
        // 代码省略...
    }
}

3、commitMutationEffects

export function commitMutationEffects(
    root: FiberRoot,
    finishedWork: Fiber,
    committedLanes: Lanes,
) {
    commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
}
function commitMutationEffectsOnFiber(
    finishedWork: Fiber,
    root: FiberRoot,
    lanes: Lanes,
) {
    const current = finishedWork.alternate;
    const flags = finishedWork.flags;

    switch (finishedWork.tag) {
        // 函数组件处理 
        case FunctionComponent: {
            recursivelyTraverseMutationEffects(root, finishedWork, lanes);
            // 执行真实DOM节点操作,如:插入、移动等
            commitReconciliationEffects(finishedWork);
            // 如果函数组件有更新
            if (flags & Update) {
                try {
                    // 执行一次useEffect的清理操作
                    // 可以得出:useEffect在每次执行前会先走一次清理逻辑,即调用useEffect返回的函数
                    commitHookEffectListUnmount(
                        HookInsertion | HookHasEffect,
                        finishedWork,
                        finishedWork.return,
                    );
                    // 执行useEffect的副作用
                    commitHookEffectListMount(
                        HookInsertion | HookHasEffect,
                        finishedWork,
                    );
                } catch (error) {
                    captureCommitPhaseError(finishedWork, finishedWork.return, error);
                }
            }
            return;
        }
        // 类组件处理
        case ClassComponent: {
            recursivelyTraverseMutationEffects(root, finishedWork, lanes);
            // 执行真实DOM节点操作,如:插入、移动等
            commitReconciliationEffects(finishedWork);
            // 处理ref对象
            if (flags & Ref) {
                if (current !== null) {
                    safelyDetachRef(current, current.return);
                }
            }

            if (flags & Callback && offscreenSubtreeIsHidden) {
                const updateQueue: UpdateQueue<mixed> | null =
                    (finishedWork.updateQueue: any);
                if (updateQueue !== null) {
                    deferHiddenCallbacks(updateQueue);
                }
            }
            return;
        }
    }
}

4、commitLayoutEffects

export function commitLayoutEffects(
    finishedWork: Fiber,
    root: FiberRoot,
    committedLanes: Lanes,
): void {
    const current = finishedWork.alternate;
    commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);
}

function commitLayoutEffectOnFiber(
    finishedRoot: FiberRoot,
    current: Fiber | null,
    finishedWork: Fiber,
    committedLanes: Lanes,
): void {
    const flags = finishedWork.flags;
    switch (finishedWork.tag) {
        // 函数组件 
        case FunctionComponent: {
            recursivelyTraverseLayoutEffects(
                finishedRoot,
                finishedWork,
                committedLanes,
            );
            // 表示组件有更新,执行Layout副作用
            if (flags & Update) {
                commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
            }
            break;
        }
        // 类组件
        case ClassComponent: {
            recursivelyTraverseLayoutEffects(
                finishedRoot,
                finishedWork,
                committedLanes,
            );
            // 表示组件有更新,会调用componentDidMount与componentDidUpdate两个生命周期函数
            if (flags & Update) {
                commitClassLayoutLifecycles(finishedWork, current);
            }

            // 调用了setState方法,处理其回调
            if (flags & Callback) {
                commitClassCallbacks(finishedWork);
            }

            // 有ref对象,则要处理ref
            if (flags & Ref) {
                safelyAttachRef(finishedWork, finishedWork.return);
            }
            break;
        }
    }
}

总结

        Commit阶段是React渲染流程的最后阶段,负责将构建好的Fiber树渲染为真实DOM节点,该阶段会处理DOM操作以及与生命周期相关的副作用等。
        Commit阶段分为Before MutationMutationLayout三个阶段,这三个阶段分别有不同的职责,最终的作用是确保Fiber树能够转为真实DOM渲染到页面中。