前情
我们已经了解到,render阶段的主要职责是:通过diff算法计算出更新结果,并构建出一棵新的Fiber树,为commit阶段的渲染做准备;render阶段结束,就表明Fiber树的构建已经结束,接下来,就进入commit阶段,整个commit阶段主要是针对root上收集到的effectList进行处理。
commit阶段的入口函数是commitRoot,函数中会调用commitBeforeMutationEffects、commitMutationEffects、commitLayoutEffects这三个函数,表示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操作后的组件状态更新和副作用执行。
- 类组件:调用组件的生命周期
componentDidMount和componentDidUpdate,调用setState的回调; - 函数组件:填充useEffect执行数组;
该阶段发生在DOM突变后,浏览器绘制之前;该阶段允许组件读取最新的布局信息,并在浏览器将新布局绘制到屏幕之前进行额外的DOM操作;
二、源码分析
1、commitRootImpl
commitRootImpl是commit阶段的入口函数,该函数是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 Mutation、Mutation、Layout三个阶段,这三个阶段分别有不同的职责,最终的作用是确保Fiber树能够转为真实DOM渲染到页面中。