前两章讲到了,react 在 render 阶段的 completeUnitWork
执行完毕后,就执行 commitRoot
进入到了 commit 阶段,本章将讲解 commit 阶段执行过程源码。
总览
commit 阶段相比于 render 阶段要简单很多,因为大部分更新的前期操作都在 render 阶段做好了,commit 阶段主要做的是根据之前生成的 effectList,对相应的真实 dom 进行更新和渲染,这个阶段是不可中断的。
commit 阶段大致可以分为以下几个过程:
- 获取 effectList 链表,如果 root 上有 effect,则将其也添加进 effectList 中
- 对 effectList 进行第一次遍历,执行
commitBeforeMutationEffects
函数来更新class组件实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数 - 对 effectList 进行第二次遍历,执行
commitMutationEffects
函数来完成副作用的执行,主要包括重置文本节点以及真实 dom 节点的插入、删除和更新等操作。 - 对 effectList 进行第三次遍历,执行
commitLayoutEffects
函数,去触发 componentDidMount、componentDidUpdate 以及各种回调函数等 - 最后进行一点变量还原之类的收尾,就完成了 commit 阶段
我们从 commit 阶段的入口函数 commitRoot
开始看:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitRoot(root) {
const renderPriorityLevel = getCurrentPriorityLevel();
runWithPriority(
ImmediateSchedulerPriority,
commitRootImpl.bind(null, root, renderPriorityLevel),
);
return null;
}
它调用了 commitRootImpl
函数,所要做的工作都在这个函数中:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitRootImpl(root, renderPriorityLevel) {
// ...
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
// ...
// 获取 effectList 链表
let firstEffect;
if (finishedWork.flags > PerformedWork) {
// 如果 root 上有 effect,则将其添加进 effectList 链表中
if (finishedWork.lastEffect !== null) {
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
firstEffect = finishedWork;
}
} else {
// 如果 root 上没有 effect,直接使用 finishedWork.firstEffect 作用链表头节点
firstEffect = finishedWork.firstEffect;
}
if (firstEffect !== null) {
// ...
// 第一次遍历,执行 commitBeforeMutationEffects
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
// ...
} else {
try {
commitBeforeMutationEffects();
} catch (error) {
// ...
}
}
} while (nextEffect !== null);
// ...
// 第二次遍历,执行 commitMutationEffects
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(
null,
commitMutationEffects,
null,
root,
renderPriorityLevel,
);
// ...
} else {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
// ...
}
}
} while (nextEffect !== null);
// 第三次遍历,执行 commitLayoutEffects
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
// ...
} else {
try {
commitLayoutEffects(root, lanes);
} catch (error) {
// ...
}
}
} while (nextEffect !== null);
nextEffect = null;
// ...
} else {
// 没有任何副作用
root.current = finishedWork;
if (enableProfilerTimer) {
recordCommitTime();
}
}
// ...
}
commitBeforeMutationEffects
commitBeforeMutationEffects
中,会从 firstEffect 开始,通过 nextEffect 不断对 effectList 链表进行遍历,若是当前的 fiber 节点有 flags 副作用,则执行 commitBeforeMutationEffectOnFiber
节点去对针对 class 组件单独处理。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
// ...
const flags = nextEffect.flags;
if ((flags & Snapshot) !== NoFlags) {
// 如果当前 fiber 节点有 flags 副作用
commitBeforeMutationEffectOnFiber(current, nextEffect);
// ...
}
// ...
nextEffect = nextEffect.nextEffect;
}
}
然后看一下 commitBeforeMutationEffectOnFiber
,它里面根据 fiber 的 tag 属性,主要是对 ClassComponent 组件进行处理,更新 ClassComponent 实例上的state、props 等,以及执行 getSnapshotBeforeUpdate 生命周期函数:
// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitBeforeMutationLifeCycles(
current: Fiber | null, finishedWork: Fiber,
): void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case Block: {
return;
}
case ClassComponent: {
if (finishedWork.flags & Snapshot) {
if (current !== null) {
// 非首次加载的情况下
// 获取上一次的 props 和 state
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
// 获取当前 class 组件实例
const instance = finishedWork.stateNode;
// ...
// 调用 getSnapshotBeforeUpdate 生命周期方法
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
// ...
// 将生成的 snapshot 保存到 instance.__reactInternalSnapshotBeforeUpdate 上,供 DidUpdate 生命周期使用
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
}
return;
}
// ...
}
}
相关参考视频讲解:进入学习
commitMutationEffects
commitMutationEffects
中会根据对 effectList 进行第二次遍历,根据 flags 的类型进行二进制与操作,然后根据结果去执行不同的操作,对真实 dom 进行修改:
- ContentReset: 如果 flags 中包含 ContentReset 类型,代表文本节点内容改变,则执行
commitResetTextContent
重置文本节点的内容 - Ref: 如果 flags 中包含 Ref 类型,则执行
commitDetachRef
更改 ref 对应的 current 的值 - Placement: 上一章 diff 中讲过 Placement 代表插入,会执行
commitPlacement
去插入 dom 节点 - Update: flags 包含 Update 则会执行
commitWork
执行更新操作 - Deletion: flags 包含 Deletion 则会执行
commitDeletion
执行更新操作
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitMutationEffects(
root: FiberRoot, renderPriorityLevel: ReactPriorityLevel,
) {
// 对 effectList 进行遍历
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);
const flags = nextEffect.flags;
// ContentReset:重置文本节点
if (flags & ContentReset) {
commitResetTextContent(nextEffect);
}
// Ref:commitDetachRef 更新 ref 的 current 值
if (flags & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current)