一、Layout阶段
1、职责
在Layout阶段,React允许组件读取最新的布局信息,并在浏览器将新布局绘制到屏幕前进行额外的DOM操作。该阶段发生在DOM突变后,浏览器绘制之前。
在该阶段,React会执行所有布局副作用LayoutEffect,如:useLayoutEffect的执行函数和componentDidMount 与 componentDidUpdate生命周期方法;
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);
}
}
}