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

232 阅读3分钟

一、Layout阶段

1、职责

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

        在该阶段,React会执行所有布局副作用LayoutEffect,如:useLayoutEffect的执行函数和componentDidMountcomponentDidUpdate生命周期方法;

2、各个函数的作用

(1)commitLayoutEffects函数
        该函数是Layout阶段的入口函数,它会调用commitLayoutEffectOnFiber函数处理Fiber节点。

(2)commitLayoutEffectOnFiber函数
        该函数针对不同的节点类型做不同的处理,主要包含:

  • 针对函数组件:处理useLayoutEffect的副作用;
  • 针对类组件:调用生命周期函数、绑定ref等;

(3)recursivelyTraverseLayoutEffects函数
        该函数的主要作用是:递归遍历Fiber节点及其子节点,执行Layout操作。

二、源码解析

1、commitLayoutEffects函数

        该函数是Layout节点的入口函数.

export function commitLayoutEffects( 
    finishedWork: Fiber, 
    root: FiberRoot, 
    committedLanes: Lanes, 
): void { 
    const current = finishedWork.alternate; 
    commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes); 
}
2、commitLayoutEffectOnFiber函数
function commitLayoutEffectOnFiber( 
    finishedRoot: FiberRoot, // 已经渲染完成的Fiber树的根节点 
    current: Fiber | null, // 当前显示在页面中的Fiber树 
    finishedWork: Fiber, // 当前已经完成的Fiber节点 
    committedLanes: Lanes, 
): void { 
    const flags = finishedWork.flags; // 获取当前Fiber节点的标记,包含需要执行的副作用类型 
    switch (finishedWork.tag) { // 根据tag处理不同类型的Fiber节点 
        case FunctionComponent: { 
            // 递归地遍历当前组件及其子树的 Layout Effects。确保所有子组件的 Layout Effects 都被执行。这是为了保证组件树从子级到父级顺序地执行副作用。 
            recursivelyTraverseLayoutEffects( 
                finishedRoot, 
                finishedWork, 
                committedLanes, 
            ); 
            if (flags & Update) { 
                // 包含Update标志,说明有更新,函数组件将会执行useLayoutEffect钩子函数 
                commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect); 
            } 
            break; 
        } 
        case ClassComponent: { 
            recursivelyTraverseLayoutEffects( 
                finishedRoot, 
                finishedWork, 
                committedLanes, 
            ); 
            if (flags & Update) { 
                // 包含Update标志,说明有更新,类组件将会调用对应的生命周期函数 
                commitClassLayoutLifecycles(finishedWork, current); 
            } 
            if (flags & Callback) { 
                // 包含Callback标志,执行组件更新后的回调函数,如:setState的回调函数 
                commitClassCallbacks(finishedWork); 
            } 
            if (flags & Ref) { 
                // 包含Ref标志,绑定ref对象到DOM元素或类组件实例上 
                safelyAttachRef(finishedWork, finishedWork.return); 
            } 
            break; 
        } 
        case HostComponent: { 
            recursivelyTraverseLayoutEffects( 
                finishedRoot, 
                finishedWork, 
                committedLanes, 
            ); 
            if (current === null && flags & Update) { 
                // 如果当前Fiber是一个新创建的Host组件(即current为null),并且有Update标志,调用commitHostComponentMount处理挂载。 
                commitHostComponentMount(finishedWork); 
            } 
            if (flags & Ref) { 
                // 将Ref附加到DOM元素上 
                safelyAttachRef(finishedWork, finishedWork.return); 
            } 
            break; 
        } 
        default: { 
            recursivelyTraverseLayoutEffects( 
                finishedRoot, 
                finishedWork, 
                committedLanes, 
            ); 
            break; 
        } 
    } 
}
3、recursivelyTraverseLayoutEffects函数

        遍历当前Fiber节点的子节点,使每个Fiber节点都会被处理到。

function recursivelyTraverseLayoutEffects( 
    root: FiberRoot, 
    parentFiber: Fiber, 
    lanes: Lanes, 
) { 
    if (parentFiber.subtreeFlags & LayoutMask) { 
        let child = parentFiber.child; 
        while (child !== null) { 
            const current = child.alternate; 
            commitLayoutEffectOnFiber(root, current, child, lanes); 
            child = child.sibling; 
        } 
    } 
}
4、useLayoutEffect Hook的执行
function commitHookLayoutEffects(finishedWork: Fiber, hookFlags: HookFlags) { 
    try { 
        commitHookEffectListMount(hookFlags, finishedWork); 
    } catch (error) { 
        captureCommitPhaseError(finishedWork, finishedWork.return, error); 
    } 
} 
// 该函数主要是执行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(); // 调用回调函数,并存储其返回值,该返回值代表的是Hook的清理函数
                inst.destroy = destroy; 
            } 
            effect = effect.next; 
        } while (effect !== firstEffect); 
    } 
}
5、类组件生命周期函数的执行
function commitClassLayoutLifecycles( 
    finishedWork: Fiber, 
    current: Fiber | null, 
) { 
    const instance = finishedWork.stateNode; 
    if (current === null) { 
        // 首次渲染阶段 
        try { 
            instance.componentDidMount(); // 组件的首次挂载 
        } catch (error) { 
            captureCommitPhaseError(finishedWork, finishedWork.return, error); 
        } 
    } else { 
        // 更新阶段 
        const prevProps = 
            finishedWork.elementType === finishedWork.type 
                ? current.memoizedProps 
                : resolveDefaultProps(finishedWork.type, current.memoizedProps);
        const prevState = current.memoizedState; 
        try { 
            instance.componentDidUpdate( // 组件的更新 
                prevProps, 
                prevState, 
                instance.__reactInternalSnapshotBeforeUpdate, 
            ); 
        } catch (error) { 
            captureCommitPhaseError(finishedWork, finishedWork.return, error); 
        } 
    } 
}
6、类组件setState回调函数的执行
function commitClassCallbacks(finishedWork: Fiber) { 
    const updateQueue: UpdateQueue<mixed> | null = 
        (finishedWork.updateQueue: any); 
    if (updateQueue !== null) { 
        const instance = finishedWork.stateNode; 
        try { 
            commitCallbacks(updateQueue, instance); 
        } catch (error) { 
            captureCommitPhaseError(finishedWork, finishedWork.return, error); 
        } 
    } 
} 
export function commitCallbacks<State>( 
    updateQueue: UpdateQueue<State>, 
    context: any, 
): void { 
    const callbacks = updateQueue.callbacks; 
    if (callbacks !== null) { 
        updateQueue.callbacks = null; 
        for (let i = 0; i < callbacks.length; i++) { 
            const callback = callbacks[i]; 
            callCallback(callback, context); 
        } 
    } 
}