React源码-commit前后阶段工作
# 0.前言
此部分内容主要涉及的 commit
中的大体逻辑,主要是讲解的起承转合的作用。
# 1.结构及作用
主要还是在 ReactFiberWorkLoop.old.js
这个文件夹下。
核心功能:处理 EffectList
链表。
# 2. 入口逻辑回顾
回顾下,当 render
结束后,会执行如下代码:
虽然下面举例的同步代码,
performConcurrentWorkOnRoot
逻辑稍有不同,基础逻辑类似:
// 调度: render 阶段 => commit 阶段
function performSyncWorkOnRoot(root) {
// 省略若干代码.....
var finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
// commit 阶段入口
commitRoot(root);
// 最后会对 RootFiber 再一次进行调度
ensureRootIsScheduled(root, now());
}
2
3
4
5
6
7
8
9
10
并且 root
根节点上会保存 finishedWork
属性值,保存当前已完成计算的树。
对于结构:
const root = ReactDOM.unstable_createRoot(document.getElementById("root"));
root.render(<App />);
function App() {
const [num, setNum] = useState(1);
return (
<div className="App">
<header className="App-header">
<p onClick={() => setNum((x) => x + 1)}>
<code title={num}>{num}</code>
</p>
</header>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
此时:
finishedWork
tag
为 3,即HostRoot
组件。
finishedWork.child
tag
为 0 ,即FunctionComponent
组件,对应于function APP(){}
finishedWork.child.child
tag
为 5,即HostComponent
组件,type
为<div />
finishedWork.firstEffect
tag
为 5,即HostComponent
组件,type
为code
# 3. commitRoot
代码及本节核心工作说明
在此阶段引入一个核心函数 runWithPriority
,以第一个参数 ImmediateSchedulerPriority
立即调用优先级,执行 commtiRootImpl
函数。
function commitRoot(root) {
runWithPriority(ImmediateSchedulerPriority, commitRootImpl.bind(null, root));
return null;
}
2
3
4
而所有的核心的逻辑,全封装在 commitRootImpl
,整体逻辑如下:
function commitRootImpl(root, renderPriorityLevel) {
// 0.准备工作
// do something before beforeMutaion
const finishedWork = root.finishedWork;
firstEffect = finishedWork.firstEffect;
// 若存在副作用节点,则执行三个循环语句
if (firstEffect !== null) {
nextEffect = firstEffect;
do {
// 1.before mutation
} while (nextEffect !== null);
do {
// 2.mutation
} while (nextEffect !== null);
do {
// 3.layout
} while (nextEffect !== null);
}
{
// No effects.
root.current = finishedWork;
}
// 4.结尾工作
// do something after layout
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
整体逻辑还是很清晰的,commit
阶段就是处理 EffectList
链表的,那首先第一件事就是需要存不存在需要更新的副作用节点。
如果需要更新,则会执行三个 do...while
循环,在此阶段中执行的核心代码(beforeMutation
、mutation
、layout
)。
至此,可总结出此阶段需要做的工作主要有五块:
- 【本节阐述】准备工作
before mutation
阶段(执行DOM
操作之前)mutation
阶段(执行DOM
操作)layout
阶段(执行DOM
操作后)- 【本节阐述】结尾工作
为了更好的叙述 commit
流程,其中 [1][2][3]
阶段的工作会单独拆分章节分析, 本节重点关注 [0][4]
流程。
# 4. commitRootImpl
准备阶段工作
# 4.1 处理 useEffect
回调
do {
// useEffect 的回调会被保存在这个 flushPassiveEffects 中
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
2
3
4
# 4.2 处理全局变量
在前面的 beginWork
以及 completeWork
阶段的分析可以知道,存在很多的全局变量用于控制函数的一个执行状态的。
比如说最重要的是 workInProgress
全局变量,其中存放着的是指向 FiberNode
的一个指针。
因此在 commit
阶段的入口,需要对这些指针进行重置操作。
if (root === workInProgressRoot) {
// We can reset these now that they are finished.
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
} else {
// This indicates that the last root we worked on is not the same one that
// we're committing now. This most commonly happens when a suspended root
// times out.
}
2
3
4
5
6
7
8
9
10
在
reconciler
协调器入口会判断workInProgress
是否为null
判断是否进入,进入commit
阶段后,保险起见重置。function workLoopSync() { while (workInProgress !== null) { performUnitOfWork(workInProgress); } }
1
2
3
4
5
# 4.3 处理 completeUnitOfWork
中留下的一个坑
观察 EffectList
链表产生的时候发现一个问题:EffectList
缺少对 root
节点的处理。
const returnFiber = completedWork.return;
if (returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags) {
}
2
3
当 workInProgress
指向 ReactDOM.render
时,是无法修改父组件的
// 结果:如果 root 存在 EffectTag,则 firstEffect 为自己;如果不存在,则直接就用第一个 firstEffect 即可。
let firstEffect;
if (finishedWork.flags > PerformedWork) {
if (finishedWork.lastEffect !== null) {
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
firstEffect = finishedWork;
}
} else {
firstEffect = finishedWork.firstEffect;
}
2
3
4
5
6
7
8
9
10
11
12
但是这种常见比较少,暂时不清楚什么情况会去修改?难道是处理 render
函数的第三个回调吗?
ReactDOM.render(<App />, document.getElementById("app"), () => {
console.log("render complete");
});
2
3
# 4.4 处理离散事件
代码如下:
if (rootsWithPendingDiscreteUpdates !== null) {
if (
!hasDiscreteLanes(remainingLanes) &&
rootsWithPendingDiscreteUpdates.has(root)
) {
rootsWithPendingDiscreteUpdates.delete(root);
}
}
2
3
4
5
6
7
8
这里出现了一个词 DiscreteUpdates
离散更新,那什么行为会触发该操作呢?常见的更新如用户的点击行为。此时 react
需要处理 focus
等事件。
# 5. commitRootImpl
结尾工作
# 5.1 判断当前节点是否会触发 useEffect
暂时跳过,后期补充,暂时不理解。
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
// useEffect 相关逻辑
if (rootDoesHavePassiveEffects) {
// This commit has passive effects. Stash a reference to them. But don't
// schedule a callback until after flushing layout work.
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {
// We are done with the effect chain at this point so let's clear the
// nextEffect pointers to assist with GC. If we have passive effects, we'll
// clear this in flushPassiveEffects.
nextEffect = firstEffect;
while (nextEffect !== null) {
const nextNextEffect = nextEffect.nextEffect;
nextEffect.nextEffect = null;
if (nextEffect.flags & Deletion) {
detachFiberAfterEffects(nextEffect);
}
nextEffect = nextNextEffect;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5.2 检测是否无限循环同步任务
此时会对同步更新进行计数,判断是否为无线更新
// 取出根节点的 Lane 优先级
remainingLanes = root.pendingLanes;
if (remainingLanes === SyncLane) {
// Count the number of times the root synchronously re-renders without
// finishing. If there are too many, it indicates an infinite update loop.
if (root === rootWithNestedUpdates) {
nestedUpdateCount++;
} else {
nestedUpdateCount = 0;
rootWithNestedUpdates = root;
}
} else {
nestedUpdateCount = 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
@todo:这里需要案例才可以更直观的了解,要不然还是一知半解。
# 5.3 继续调度
这段代码出现很多次了,但是还是无法了解的很深入
ensureRootIsScheduled(root, now());
# 5.4 useLayoutEffect 回调执行流程
源码部分:
flushSyncCallbackQueue();
这部分可以结合案例说明:
function App() {
const [num, setNum] = useState(1);
useLayoutEffect(() => {
if (num === 2) {
setNum(num + "Layout!");
}
}, [num]);
return (
<div className="App">
<header className="App-header">
<p onClick={() => setNum((x) => x + 1)}>
<code title={num}>{num}</code>
</p>
</header>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
点击 1
→ 2
时,打断点如下:
// debugger;
flushSyncCallbackQueue(); // 执行 useLayout Effect 回调
2
可以发现在断点处,页面显示状态为 2
,此时开始执行 flushSyncCallbackQueue
:
执行后,页面会进入
performSynWorkOnRoot
同步执行逻辑。在 ``performSynWorkOnRoot
函数中此时会再次执行
render和
commit阶段,对应进入的函数名为
renderRootSync和
commitRoot(root)`。无乱是
concurrent
和legacy
进入的都是commitRoot
这个函数,并不会在函数名上做区分,但在commitRoot
此时调度会使用runWithPriority
来调度更新逻辑,由于performSyncWorkOnRoot
会进入同步调度逻辑。在同步阶段的
mutation
阶段,即第二个while
循环结束后,会触发页面视图更新,将2
变更为2Layout!
# 6. 完整 commitRootImpl 工作流程梳理
伪代码如下:
function commitRootImpl(root, renderPriorityLevel) {
do {
// 执行 useEffect 回调函数,由于 useEffect 会一直存在回调。
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
const finishedWork = root.finishedWork;
// 清空一些全局变量
root.finishedWork = null;
if (root === workInProgressRoot) {
// We can reset these now that they are finished.
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
}
// 处理离散事件(如鼠标的点击等,跳过)
if (rootsWithPendingDiscreteUpdates !== null) {...}
// 处理 FiberRoot 的 firstEffect 链表
firstEffect = finishedWork.firsetEffect;
// 若存在副作用节点,则执行三个循环语句
if(firstEffect !== null){
nextEffect = firstEffect;
do {
// 1.before mutation
} while (nextEffect !== null);
do {
// 2.mutation 【视图更新】
} while (nextEffect !== null);
do {
// 3.layout
} while (nextEffect !== null);
}{
// No effects.
root.current = finishedWork;
}
// 对 useEffect 的处理
// 判断是是否为无限循环更新
// 继续调度
ensureRootIsScheduled(root, now());
// 触发 useLayoutEffect → performSyncWorkOnRoot
flushSyncCallbackQueue()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 待解释内容:
- 如何让某段函数以一个优先级执行?解析
runWithPriority
函数。
- rootWithPendingPassiveEffects 这函数是怎么改变的?
- flushPassiveEffects 是怎么产生的?
补充机制:在 React 中是如何对性能进行监控的?
# 补充内容:
在 SchedulerWithReactIntegration
文件中,约定了优先级的等级:
export const ImmediatePriority: ReactPriorityLevel = 99;
export const UserBlockingPriority: ReactPriorityLevel = 98;
export const NormalPriority: ReactPriorityLevel = 97;
export const LowPriority: ReactPriorityLevel = 96;
export const IdlePriority: ReactPriorityLevel = 95;
// NoPriority is the absence of priority. Also React-only.
export const NoPriority: ReactPriorityLevel = 90;
2
3
4
5
6
7