|
|
|
React 的 commit 阶段从 commitRoot 这个函数开始,从 performSyncWorkOnRoot 函数中被调用,接收一个名为 root 的参数,这个 root 就是从 Fiber 递归中完成递归流程的 WorkInProgress Fiber 树
|
|
|
|
|
|
|
|
commitRoot 在 React 18 的代码和 React 17 有着较大的不同,但是最终的目的都是类似的:调用 commitRootImpl 函数,在 React 17 中,commitRootImpl 和一个优先级一起作为参数交由 runWithPriority 函数,而 React 18 中则是直接执行 commitRootImpl 函数
|
|
|
|
|
|
|
|
commitRoot 的代码很少,其中最主要的是执行 commitRootImpl 函数,也就是说 commit 阶段最核心的任务就发生在 commitRootImpl 中,在 React 18 中,commitRootImpl 函数发生了比较大的变化,以往 17 中,有三个主要的循环,这三个循环主要代表了 commit 的阶段的三个时刻:分别是 before、mutation 和 layout,也对应了三个函数 commitBeforeMutationEffect、commitMutationEffects 和commitLayoutEffects,18 中这三个函数依旧存在,但已经不是在循环被执行了,以下的文章内容会先从 React 17 开始,然后再探讨在 React 18 中发生的变化~~如果我不懒的话~~
|
|
|
|
|
|
|
|
进入 commitRootImpl 函数内部,首先会执行一个 do..while 循环
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
do {
|
|
|
|
flushPassiveEffects();
|
|
|
|
} while (rootWithPendingPassiveEffects !== null);
|
|
|
|
```
|
|
|
|
|
|
|
|
这个循环的跳出条件是 rootWithPendingPassiveEffects 等于 null,不然就执行 flushPassiveEffects 函数,那么 rootWithPendingPassiveEffects 是什么?为什么要执行 flushPassiveEffects 函数,这个 rootWithPendingPassiveEffects 就是在 completeWork 阶段生成的带有 effectTag 的链表 effectList,而 flushPassiveEffects 函数内部会遍历这个链表,然后执行其各自内部的 useEffect 中的回调函数,在 commitRootImpl 开头执行是因为这些回调函数有可能触发新的渲染,所以需要遍历直到没有任务
|
|
|
|
|
|
|
|
接下来会执行 flushRenderPhaseStrictModeWarningsInDEV 函数,这个函数从名字上看出之和开发环境下有关,负责 React 中的 StrictMode
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
flushRenderPhaseStrictModeWarningsInDEV();
|
|
|
|
```
|
|
|
|
|
|
|
|
中间跳过一段逻辑无关和 performance 监控代码,进入对 Fiber Tree 和 effecrList 的一系列初始化
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
......
|
|
|
|
|
|
|
|
root.finishedWork = null;
|
|
|
|
root.finishedLanes = NoLanes;
|
|
|
|
|
|
|
|
root.callbackNode = null;
|
|
|
|
root.callbackPriority = NoLane;
|
|
|
|
|
|
|
|
// 内部位运算
|
|
|
|
var remainingLanes = mergeLanes(
|
|
|
|
finishedWork.lanes,
|
|
|
|
finishedWork.childLanes
|
|
|
|
);
|
|
|
|
|
|
|
|
// 内部也是位运算
|
|
|
|
markRootFinished(root, remainingLanes);
|
|
|
|
|
|
|
|
if (rootsWithPendingDiscreteUpdates !== null) {
|
|
|
|
if (
|
|
|
|
!hasDiscreteLanes(remainingLanes) &&
|
|
|
|
rootsWithPendingDiscreteUpdates.has(root)
|
|
|
|
) {
|
|
|
|
rootsWithPendingDiscreteUpdates.delete(root);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (root === workInProgressRoot) {
|
|
|
|
workInProgressRoot = null;
|
|
|
|
workInProgress = null;
|
|
|
|
workInProgressRootRenderLanes = NoLanes;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 以下省略
|
|
|
|
```
|
|
|
|
|
|
|
|
最终走到 commitRootImpl 的第一个主要循环
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
do {
|
|
|
|
{
|
|
|
|
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
|
|
|
|
if (hasCaughtError()) {
|
|
|
|
if (!(nextEffect !== null)) {
|
|
|
|
{
|
|
|
|
throw Error( "Should be working on an effect." );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var error = clearCaughtError();
|
|
|
|
captureCommitPhaseError(nextEffect, error);
|
|
|
|
nextEffect = nextEffect.nextEffect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (nextEffect !== null);
|
|
|
|
```
|
|
|
|
|
|
|
|
这个循环内部由 invokeGuardedCallback 执行 commitBeforeMutationEffects 函数,commitBeforeMutationEffects 就是开头说到的负责 before 阶段的函数,具体函数的深入可以看 [[React 的深入探索 - commitBeforeMutationEffect]]
|
|
|
|
|
|
|
|
然后根据 hasCaughtError 函数的返回值,执行 captureCommitPhaseError 函数,这个函数和 React 的 Error Boundaries (错误边界)有关,这里不展开谈,后边的两个阶段的逻辑里边也有着类似的逻辑,从这里可以看出来,Error Boundaries 会捕获 commit 阶段的错误
|
|
|
|
|
|
|
|
循环的跳出条件是 nextEffect 等于 null,也就是这个循环会遍历 effectList,后边的两个主要循环的跳出条件也是相同的
|
|
|
|
|
|
|
|
跳出 before 阶段的循环之后进入第二个主要循环:mutation
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
do {
|
|
|
|
{
|
|
|
|
invokeGuardedCallback(
|
|
|
|
null,
|
|
|
|
commitMutationEffects,
|
|
|
|
null,
|
|
|
|
root,
|
|
|
|
renderPriorityLevel
|
|
|
|
);
|
|
|
|
if (hasCaughtError()) {
|
|
|
|
if (!(nextEffect !== null)) {
|
|
|
|
{
|
|
|
|
throw Error( "Should be working on an effect." );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var _error = clearCaughtError();
|
|
|
|
captureCommitPhaseError(nextEffect, _error);
|
|
|
|
nextEffect = nextEffect.nextEffect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (nextEffect !== null);
|
|
|
|
```
|
|
|
|
|
|
|
|
和 before 的循环非常类似,commitMutationEffects 也是有 invokeGuardedCallback 调用,也有着相同 Error Boundaries 的逻辑
|
|
|
|
|
|
|
|
关于 commitMutationEffects: [[React 的深入探索 - commitMutationEffects]]
|
|
|
|
|
|
|
|
最后一个阶段:layout 阶段
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
do {
|
|
|
|
{
|
|
|
|
invokeGuardedCallback(
|
|
|
|
null,
|
|
|
|
commitLayoutEffects,
|
|
|
|
null,
|
|
|
|
root,
|
|
|
|
lanes
|
|
|
|
);
|
|
|
|
if (hasCaughtError()) {
|
|
|
|
if (!(nextEffect !== null)) {
|
|
|
|
{
|
|
|
|
throw Error( "Should be working on an effect." );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var _error2 = clearCaughtError();
|
|
|
|
captureCommitPhaseError(nextEffect, _error2);
|
|
|
|
nextEffect = nextEffect.nextEffect;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} while (nextEffect !== null);
|
|
|
|
```
|
|
|
|
|
|
|
|
关于 commitLayoutEffects: [[React 的深入探索 - commitLayoutEffects]]
|