commit阶段流程
主要分为三个阶段:
- before mutation
- mutation
- layout
finishedWork节点是render阶段结束挂载在wipHostRootFiber上的一个属性,是已经打上flags后的新的根fiber。
在commit阶段首先会判断是否需要进入三阶段:
const subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
const rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {
} else {
// No effects.
root.current = finishedWork;
// Measure these anyway so the flamegraph explicitly shows that there were
// no effects.
}
没有相应的flags则会跳过commit阶段,其中三阶段相关的flags常量为:
export const BeforeMutationMask = Update | Snapshot;
export const MutationMask = Placement | Update | ChildDeletion | ContentReset | Ref | Hydrating | Visibility;
export const LayoutMask = Update | Callback | Ref | Visibility;
export const PassiveMask = Passive | ChildDeletion;
子阶段的执行流程大致分为commitXXXEffects_begin和commitXXXEffects_complete
function commitXXXEffects(
root: FiberRoot,
firstChild: Fiber,
) {
// 省略全局变量初始化等操作
// firstChild 为 wipHostRootFiber节点上的 finishedWork属性,即内存中已经构建好的离屏fiber树
nextEffect = firstChild;
commitXXXEffects_begin();
}
在commitXXXEffects_begin中主要递归fiber树,执行该子阶段对应的处理函数,并找到第一个不包含该子阶段flags的fiberNode,执行该子阶段的complete方法
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
const fiber = nextEffect;
const child = fiber.child;
// 执行相关子阶段特有操作
// ...
// 找到第一个不包含该子阶段flags的fiberNode,执行该子阶段的complete方法
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
child.return = fiber;
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
在commitBeforeMutationEffects_complete中主要是归阶段,除了执行该子阶段对应的complete方法外,会开启sibling节点的begin流程,如果没有sibling节点,则执行父节点的complete方法。
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
const fiber = nextEffect;
setCurrentDebugFiberInDEV(fiber);
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentDebugFiberInDEV();
const sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
before mutation
此子阶段的主要逻辑在commitBeforeMutationEffectsOnFiber中,主要作用为:
- 对于Class Component,执行
getSnapshotBeforeUpdate方法 - 清空
HostRoot挂载的内容,为mutation阶段做准备
mutation
此子阶段主要做dom相关操作,包括dom的增、删、改。
删除节点
当删除一个节点时,需要考虑:
- 子树中所有组件的unmount逻辑
- 子树中ref的卸载
- 子树中Effect相关的hook(
useEffect,useLayoutEffect)的destory回调
插入及移动节点
当插入一个节点时,需要执行:
- 从当前fibernode向上遍历,找到第一个类型为
HostComponent、HostRoot、HostPortal的fibernode - 获取fibernode的稳定兄弟节点(不能被标记为Placement)
- 如果存在稳定兄弟节点,会插入到兄弟节点之前,如果不存在,会插入到父节点的最后一个子节点的位置
更新节点
获取到fibernode上的stateNode属性,即真实dom节点,同时获取newProps(wipFiberNode.memoizedProps)、oldProps(current.memoizedProps)和updateQueue(),
将
- style属性变化
- innerHtml
- 文本节点变化
- 其他元素属性变化
更新到dom
Fiber Tree 切换
完成mutation阶段后会进行fiber 树的切换,即
root.current = finishedWork;
layout
主要执行
useLayoutEffect的回调componentDidMount/Update的回调- 部分fiber节点上updateQueue的执行