effect
# 概述
React将副作用分为三层:Insertion → Layout → Passive
这三个副作用的作用如下:
| 阶段 | Hook | 解决的核心问题 | 用途 |
|---|---|---|---|
| Insertion | useInsertionEffect | 样式必须在 DOM 读取前就存在(防闪烁) | 1.css-in-JS 2.动态样式注入 |
| Layout | useLayoutEffect | 需要同步读取/修改 DOM 布局 | 1.读取布局(宽高、位置)2.同步调整DOM(scroll,动画起点) |
| Passive | useEffect | 非阻塞副作用(请求、订阅等) | 不影响UI布局,若异步请求,订阅事件、日志上报 |
# 流程分析
# 一、EffectMount过程
Effect Mount是指函数组件在首次渲染挂载时,effect副作用首次会被调用执行。这发生在组件渲染的beginWork阶段,会先切换hooks分发器dispatcher。以useEffecthooks为例,在函数组件首次执行时,useEffect会在函数内部调用,此时useEffect本质上就是调用执行mountEffect函数。这一过程主要就是收集useEffect的标记保存到函数组件对应的fiber.flags中,以便在后续commit阶段中调用useEffect的create即第一个参数(函数)。
# 1.1EffectMount架构图
flowchart TD
A["useEffect(即mountEffect)"] --> B["
调用mountEffectImpl公共方法
1.构建Hook
2.记录effect标记到当前Fiber的flags上
"]
B --> C["调用pushSimpleEffect方法"]
C --> D{"当前渲染Fiber是否存在更新队列"}
D --不存在--> E["新建队列,作为当前渲染Fiber的更新队列"]
D --存在-->F["获取更新队列的lastEffect"]
E -->F
F -->G{"lastEffect是否为null"}
G --是-->H["直接将effect入队\n单独构成环,effect.next指向自身"]
G --否-->I["将effect插入到队列环的末尾\n更新指向"]
H --> J[return effecct]
I --> J
J --effect保存到hook的memoizedState--> B
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1.2EffectMount阶段相关方法的源码解析
# 1.2.1mountEffect方法
useEffect在mount时的入口函数。接受两个参数:create方法和 deps依赖数组
function mountEffect(create, deps) {
mountEffectImpl(
PassiveEffect | PassiveStaticEffect, // 用于Fiber上的标记
HookPassive,// 用于在UpdateQueue链表中标识effect的类别
create,// 回调函数
deps) // 依赖数组
}
2
3
4
5
6
7
# 1.2.2mountEffectImpl方法
mountEffectImpl是一个副作用的公共方法,useEffect、useLayout和useInsertionEffect都会调用,只是传参不同。
mountEffectImpl方法内部会调用mountWorkInProgressHook构建hook,调用pushSimpleEffect方法构建effect,并且会将fiberFlags标记保存到渲染fiber的flags上,以及effect绑定到fiber的memoizedState的单向链表上。
function mountEffectImpl(fiberFlags, hooksFlags, create, deps) {
// 构建hook
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
const instance = { destroy: undefined };
// effect ->fiber.memoizedState链表上
hook.memoizedState = pushSimpleEffect(
HookHasEffect | hooksFlag,
instance,
create,
nextDeps,
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.2.3pushSimpleEffect方法
pushSimpleEffect同样是一个公共的方法,主要就是构建effect,并将其推入到fiber的updateQueue环状队列的末尾,最后返回effect。
pushSimpleEffect方法在mount和update阶段中都会调用。
function pushSimpleEffect(tag, inst, create, deps) {
// 构建effect数据结构
const effect = {
tag, // 副作用标记
create, // 回调方法
deps, // 依赖数组
inst, // 保存副作用的清理函数 {destory}
next: null // 指向下一个effect
}
// 获取当前fiber的updateQueue更新队列
let componentUpdateQueue = currentlyRenderingFiber.updateQueue;
// 若更新队列为null
if (componentUpdateQueue === null) {
// 创建更新队列
componentUpdateQueue = {
lastEffect: null,//最后一个effect
events: null,
stores: null,
memoCache: null,
};
// 将更新队列保存到fiber的updateQueue
currentlyRenderingFiber.updateQueue = componentUpdateQueue;
}
// 获取更新队列的末尾effect
const lastEffect = componentUpdateQueue.lastEffect;
// 若lastEffect为null
if (lastEffect === null) {
// 将effect赋值到更新队列的lastEffect,并且effect.next指向自身,构成一个环
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
// 若lastEffect不为null,则说明前面就存在副作用,需要将effect插入到环队列的末尾
// 保存第一个effect,lastEffect.next始终指向第一个节点
const firstEffect = lastEffect.next;
// 将lastEffect.next指向effect
lastEffect.next = effect;
// 将effect.next指向第一个effect
effect.next = firstEffect;
// 将更新队列的lastEffect指向effect
componentUpdateQueue.lastEffect = effect;
}
// 返回 effect
return effect
}
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
# 1.3 挂载useEffect/useLayoutEffect/useInsertionEffect区别
useLayoutEffect和useInsertionEffect与useEffect的区别就是入口函数不同,它们对应的tag标记不同,以及在fiber上的标记也不同;tag标记用于区分updateQueue上的effect类别,方便后续从updateQueue上读取正确的effect;而在fiber.flag上的不同,可以用于在最后的commitRoot阶段实现不同类型的副作用调用不同的方法。以下是mout阶段它们的区别
| effect种类 | useEffect | useLayoutEffect | useInsertionEffect |
|---|---|---|---|
| 入口函数 | mountEffect | mountLayoutEffect | mountInsertionEffect |
tag标记 | HasEffect与Passive | HasEffect与Layout | HasEffect与 Insertion |
在fiber上标记 | fiber.flag与PassiveEffect 与 PassiveStaticEffect | fiber.flag与UpdateEffect与 LayoutStaticEffect | fiber.flag与UpdateEffect |
# 二、EffectUpdate过程
Effect Update表示函数组件在更新时,effect也会检测更新,同样地,它也发生在组件渲染的 beginWork阶段。函数组件在执行更新前,hooks的分发器dispatcher会切换,此时的useEffect就不再是mount阶段的mountEffect,而是updateEffect。
# 2.1.EffectUpdate架构图
flowchart TD
A["useEffect"] --> B["调用updateEffectImpl公共方法"]
B --> C["调用updateWorkInProgressHook构建新hook"]
C --> D{"判断currentHook是否不为null"}
D --不为null--> E{"依赖deps是否不为null"}
E --不为null-->F{"调用areHookInputsEqual判断依赖是否发生变化"}
F --没变化-->G["调用pushSimpleEffect方法\n构建新的effect,放入到队列中\neffect保存到新hook"]
G --> H["return"]
D --为null-->I[记录fiberFlags到当前Fiber的flags上]
I --> J["调用pushSimpleEffect方法\n构建新的effect,放入到队列中\neffect保存到新hook"]
2
3
4
5
6
7
8
9
10
# 2.2Effect Update阶段相关方法的源码解析
# 2.2.1updateEffect方法
useEffect在更新时实际执行的是updateEffect方法,是一个入口函数,其内部就是调用updateEffectImpl方法。
function updateEffect(create,deps){
// Passive和HookPassive都是useEffect在更新阶段的标记
updateEffectImpl(Passive,HookPassive,create,deps);
}
2
3
4
# 2.2.2updateEffectImpl方法
updateEffectImpl方法会被useEffect/useLayoutEffect/useInsertionEffect副作用在更新阶段的入口函数调用,只是参数不同。
updateEffectImpl方法内部会判断副作用的依赖是否发生改变,若发生了改变,则需要在fiber上打上标记。
function updateEffectImpl(fiberFlags,hookFlags,create,deps){
// 调用updateWorkInProgressHook获取当前的新hook
const hook = updateWorkInProgressHook();
const nexDeps = deps === undefined ? null : deps;
// 复用effct的inst
const effect = hook.memoizedState;
const inst= effect.inst;
// 若存在旧hook
if(currentHook !== null){
// 新依赖不为null
if(nextDeps!== null){
// 从旧 hook上获取旧的依赖prevDeps
const prevEffect = currentHook.meoizedState;
const prevDeps = prevEffect.deps;
// 调用areHookInputsEqual判断依赖是否发生变化
if(areHookInputsEqual(nextDeps,prevDeps)){
// 若无变化,则调用pushSimpleEffect方法创建新effect,插入到当前Fiber的updateQueue末尾
// 将新effect 保存到新hook的memoizedState上
hook.memoizedState = pushSimpleEffect(hookFlags,inst,create,nextDeps);
// 直接返回,后续就在commit阶段就不会触发
return;
}
}
}
// 上述任一条件不满足:不存在旧 hook,新的nextDeps为null或者依赖发生了变化,则说明需要向Fiber.flags上标记
currentlyRenderingFiber.flags |= fiberFlags;
// 调用pushSimpleEffect创建新effect,和上面不同的是标记不同,这里标记加上了HookHasEffect,表示在commitRoot阶段需要执行create方法
hook.memoizedState = pushSimpleEffect(HookHasEffect|hookFlags,inst,create,nextDeps);
}
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
# 2.3 useEffect/useLayoutEffect/useInsertionEffect
在函数组件更新时,这三者也是同挂载阶段一样,入口函数不同,标记不同;只有当依赖deps发生改变时,才会在fiber上更新flag,但是无论是否需要更新,都需要生成新的effect以保持updateQueue的顺序连贯性。以下是它们的区别:
| 更新阶段 | useEffect | useLayoutEffect | useInsertionEffect |
|---|---|---|---|
不需要更新的tag | Passive | Layout | Insertion |
需要更新时的tag | HookHasEffect与Passive | HookHasEffect与Layout | HookHasEffect与Insertion |
fiber.flag | Passive | UpdateEffect | UpdateEffect |
# 三、EffectRun过程
useEffect副作用的执行发生在commitRoot 阶段,其相关的流程图如下:
flowchart TD
A["commitRoot"]-->B{"判断fiber节点\nsubtreeFlags或flags\n与PassiveMask的关系"}
B --存在交集-->C["调用sceduleCallback安排微任务flushPassiveEffects"]
B --无交集-->D["flushMountEffects"]
D -->E["flushLayoutEffect"]
E --调度-->C
2
3
4
5
6
PassiveMask掩码与Passive的关系如下:
const PassiveMask = Passive | ChildDeletion
即在commitRoot 阶段,若fiber节点中的标记flags或者是子节点的标记subFlags中记录了存在useEffect(Passive标记)或者子节点删除(ChildDeletion标记),则通过调度器scheduler的scheduleCallback方法进行调度异步执行flushPassiveEffects,优先级为NormalSchedulerPriority,flushPassiveEffects方法就是执行useEffect副作用;后续就是同步调用flushLayoutEffect方法,执行useLayouteffect副作用;这点正好可以解释在同一个函数组件内,useLayoutEffect比useEffect先执行。
# 3.1.useEffect调度
# 3.1.1 flushPassiveEffects方法
flushPassiveEffect流程图
flowchart TD
A["flushPassiveEffects"] --> B{"是否存在待执行的useEffect"}
B --不存在-->C["返回 false"]
B --存在-->D["优先级转换和切换渲染上下文"]
D -->E["调用flushPassiveEffectsImpl,并返回其结果true"]
E --执行完了-->F["恢复上下文和处理缓存"]
E --其内部实现-->G["消费队列+防止重复执行"]
G -->H{"检查执行上下文是否处于render或者commit阶段"}
H --是-->I["报错:会陷入循环调用"]
H --否-->J[切换执行上下文]
J-->K[执行卸载副作用\ncommitPassiveUnmountOnFiber]
K-->L[执行挂载副作用\ncommitPassiveMountOnFiber]
L-->M["恢复执行上下文"]
M-->N["触发同步更新"]
N--返回true-->E
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function flushPassiveEffects() {
// pengdingEffectsstatus表示当前effects的状态
// 若当前没有待执行的useEffect,则直接返回
if (pendingEffectsStatus !== PENDING_PASSIVE_PHASE) {
return false;
}
// 取出当前root和useEffect的剩余优先级
const root = pendingEffectsRoot;
const remainingLanes = pendingEffectsRemainingLanes;
// 清空effect优先级
pendingEffectsRemainingLanes = NoLanes;
// 将effect对应的lanes转为调度的优先级
const renderPriority = lanesToEventPriority(pendingEffectsLanes);
// 保存当前调度的上下文
const prevTransition = ReactSharedInternals.T;
const previousPriority = ReactSharedInternals.p;
try {
// 设置执行effect的上下文,将优先级提升到至少DefaultLane
// 清空transition (effect不属于transition)
ReactSharedInternals.p = DefaultLane > renderPriority ? DefaultLane : renderPriority;
ReactSharedInternals.T = null;
// 实际执行useEffect
return flushPassiveEffectsImpl();
} finally {
// 恢复上下文
ReactSharedInternals.p = previousPriority;
ReactSharedInternals.T = prevTransition;
// 释放缓存
releaseRootPooledCache(root, remainingLanes);
}
}
function flushPassiveEffectsImpl() {
// 清空全局状态
// 将所有待执行effect的上下文全部取出,然后立即清空全局变量
// 这是典型的“消费队列+防止重复执行”
const transitions = pendingPassiveTransitions;
pendingPassiveTransitions = null;
const root = pendingEffectsRoot;
const lanes = pendingEffectsLanes;
pendingEffectsStatus = NO_PENDING_EFFECTS;
pendingEffectsRoot = null;
pendingFinishedWork = null;
pendingEffectsLanes = NoLanes;
// 检查当前执行的上下文是否是render或者commit阶段,若是,则报错,
// 因为useEffect中可能调用setState,导致无限渲染
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Cannot flush passive effects while already rendering.');
}
// 切换上下文
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
// 执行卸载副作用
commitPassiveUnmountOnFiber(root.current);
// 执行挂载副作用
commitPassiveMountEffects(
root,
root.current,
lanes,
transitions,
pendingEffectsRenderEndTime,
);
//恢复上下文
executionContext = prevExecutionContext;
//触发同步更新
flushSyncWorkOnAllRoots();
//返回true
return true;
}
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
由上可知,在更新阶段,先卸载副作用(执行上一次的清理函数destroy),再挂载副作用(执行此次的create方法)
# 3.1.2 卸载副作用
流程图如下
flowchart TD
A["commitPassiveUnmountOnFiber"] --> B{"是函数组件/forwardRef组件/memo组件?"}
B --是(1)-->C[调用recursivelyTraversePassiveUnmountEffects]
B --是(2)-->D{fiber中是否有useEffect标记?}
D --有-->E["调用commitHookEffectListUnmount,执行队列中的清理方法"]
C --(1)-->F{是否存在子节点删除的情况?}
F ---存在-->G[遍历子节点删除标记\n调用commitPassiveUnmountEffectsInsideOfDeletedTree_begin]
G-->H[调用detachAlternateSiblings]
C --(2)-->I{判断子节点是否存在useEffect}
I --是-->J[DFS遍历子节点]
J --调用-->A
2
3
4
5
6
7
8
9
10
11
# 3.1.2.1 commitPassiveUnmountOnFiber方法
commitPassiveUnmountOnFiber方法会根据fiber的tag类型进行操作,当fiber是函数组件/ForwardRef组件/SimpleMemoComponent组件之一时,则调用recursivelyTraversePassiveUnmountEffects先进行子节点的副作用挂载,再进行自身副作用的执行,即先子后父。
function commitPassiveUnmountOnFiber(finishedWork){
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraversePassiveUnmountEffects(finishedWork);
// 判断当前fiber上是否有useEffect,若有,则执行commitHookEffectListUnmount方法
if (finishedWork.flags & Passive) {
commitHookEffectListUnmount(
HookPassive | HookHasEffect,
finishedWork,
finishedWork.return,
);
}
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.1.2.2 recursivelyTraversePassiveUnmountEffects方法
function recursivelyTraversePassiveUnmountEffects(parentFiber) {
// useEffect在组件卸载时也会执行,因此先判断fiber中是否有子组件删除的标记,若有,则进行遍历,调用commitPassiveUnmountEffectsInsideOfDeletedTree_begin方法,这里不展开
const deletions = parentFiber.deletions;
if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
nextEffect = childToDelete;
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
childToDelete,
parentFiber,
);
}
}
detachAlternateSiblings(parentFiber);
}
// 判断子组件中是否存在useEffect,若有则对其进行遍历,即DFS
if (parentFiber.subtreeFlags & PassiveMask) {
let child = parentFiber.child;
while (child !== null) {
// 调用commitPassiveUnmountOnFiber,
commitPassiveUnmountOnFiber(child);
// 在遍历兄弟节点
child = child.sibling;
}
}
}
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
# 3.1.2.3 commitHookEffectListUnmount方法
commitHookEffectListUnmount方法是一个公共方法,本质上就是遍历fiber.updateQueue,执行队列中的effect的清理函数,具体执行updatQueue中的哪些effect,由参数flags决定。
export function commitHookEffectListUnmount(
flags,
finishedWork,
nearestMountedAncestor,
) {
try {
// 从函数队列中取出更新队列
const updateQueue = finishedWork.updateQueue;
// 判断队列是否存在最后一个useEffect节点
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
// 判断最后一个节点是否存在
if (lastEffect !== null) {
// 通过最后一个effect,找到第一个effect
const firstEffect = lastEffect.next;
let effect = firstEffect;
// 从第一个effect按顺序遍历
do {
// 判断传入的flags是否时effect.tag的子集
if ((effect.tag & flags) === flags) {
// 获取清理函数destroy
const inst = effect.inst;
const destroy = inst.destroy;
// 判断destroy方法是否存在
if (destroy !== undefined) {
// 若存在,则先清空effect副作用中的清理函数
inst.destroy = undefined;
// 再执行
destroy();
}
}
// 遍历下一个effect
effect = effect.next;
} while (effect !== firstEffect);
}
} catch (error) {
// 报错
}
}
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
# 3.1.3 挂载副作用
流程图如下
flowchart TD
A["commitPassiveMountOnFiber"] --> B{"是函数组件/forwardRef组件/memo组件?"}
B --是(1)-->C[调用recursivelyTraversePassiveMountEffects]
B --是(2)-->D{fiber中是否有useEffect需要调度的标记?}
D --有-->E["调用commitHookEffectListMount\nn执行队列中的create方法\ncreate的执行结果保存到destory中"]
C --是-->I{判断子节点是否存在useEffect更新}
I --是-->J[DFS遍历子节点]
J --调用-->A
2
3
4
5
6
7
8
# 3.1.3.1 commitPassiveMountOnFiber方法
挂载副作用的入口函数是commitPassiveMountOnFiber,它和commitPassiveMountOnFiber类似,也是根据finishedRWork.tag来决定下一步。
function commitPassiveMountOnFiber(finishedRoot, finishedWork, committedLanes, committedTransitions) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraversePassiveMountEffects(
finishedRoot,
finishedWork,
committedLanes,
committedTransitions,
endTime,
);
if (flags & Passive) {
commitHookPassiveMountEffects(
finishedWork,
HookPassive | HookHasEffect,
);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3.1.3.2 recursivelyTraversePassiveMountEffects方法
recursivelyTraversePassiveMountEffects就是判断子节点上是否存在子节点的effect需要调度,若是,则进行DFS遍历调用commitPassiveMountOnFiber方法
function recursivelyTraversePassiveMountEffects(
root,
parentFiber,
committedLanes,
committedTransitions
) {
if (
parentFiber.subtreeFlags & PassiveMask
) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveMountOnFiber(
root,
child,
committedLanes,
committedTransitions,
0,
);
child = child.sibling;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3.1.3.3 commitHookEffectListMount方法
commitHookEffectListMount方法也是一个公共方法,本质上就是调度队列上的effect,执行满足条件的effect的create方法,并将其返回结果保存,作为下一次调度的清理函数。其实现如下:
function commitHookEffectListMount(flags, finishedWork) {
try {
const updateQueue = finishedWork.updateQueue;
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
if (lastEffect !== null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
let destroy;
const create = effect.create;
const inst = effect.inst;
destroy = create();
inst.destroy = destroy;
}
effect = effect.next;
} while (effect !== firstEffect)
}
} catch () {
// < !--报错-->
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3.2 useLayoutEffect调度
useLayoutEffect的调度会在DOM变更后,渲染前调度,其入口函数是flushLayoutEffects方法。
# 3.2.1 flushLayoutEffects方法
flushLayotEffect方法和flushPassiveEffects方法类似,会涉及到fiber.flag以及fiber.subtreeFlags与useLayoutEffect的标记判断,满足条件则说明fiber或其子节点中存在需要执行的useLayout,还有上下文的切换与恢复等。其核心是调用commitLayoutEffectOnFiber方法。
function flushLayoutEffects(){
// 判断shi fou是否到了执行useLayoutEffect阶段
if (pendingEffectsStatus !== PENDING_LAYOUT_PHASE) {
return;
}
//
pendingEffectsStatus = NO_PENDING_EFFECTS;
const root = pendingEffectsRoot;
const finishedWork = pendingFinishedWork;
const lanes = pendingEffectsLanes;
const subtreeHasLayoutEffects =
(finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
const rootHasLayoutEffect = (finishedWork.flags & LayoutMask) !== NoFlags;
if (subtreeHasLayoutEffects || rootHasLayoutEffect) {
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = null;
var previousPriority = ReactDOMSharedInternals.p;
ReactDOMSharedInternals.p = DiscreteEventPriority;
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
try {
const current=finishedWork.alternate;
commitLayoutEffectOnFiber(root,current,finishedWork,committedLanes)
} finally {
executionContext = prevExecutionContext;
ReactDOMSharedInternals.p = previousPriority
ReactSharedInternals.T = prevTransition;
}
}
}
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
# 3.2.2 commitLayoutEffectOnFiber方法
commitLayoutEffectOnFiber方法会根据finishedWork.tag类型来决定下一步,和useEffect中也很类似,先执行子节点的useLayoutEffect(若满足条件),再执行父节点调用commitHookLayoutEffects方法。而commitHookLayoutEffects方法实质上就是调用commitHookLayoutEffects方法,该方法在useEffect调度中也讲过。
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
recursivelyTraverseLayoutEffects(
finishedRoot,
finishedWork,
committedLanes,
);
if (flags & Update) {
commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
}
break;
}
}
}
function recursivelyTraverseLayoutEffects(
root,
parentFiber,
lanes,
) {
if (parentFiber.subtreeFlags & LayoutMask) {
let child = parentFiber.child;
while (child !== null) {
const current = child.alternate;
commitLayoutEffectOnFiber(root, current, child, lanes);
child = child.sibling;
}
}
}
function commitHookLayoutEffects(finishedWork,hookFlags){
commitHookEffectListMount(hookFlags, finishedWork);
}
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
# 3.3useInsertionEffect调度
useInsertionEffect副作用就是在flushMuationEffects方法中调用,该方法就是会进行DOM的增删改显隐等操作。
大体流程和useEffect/useLayoutEffect的流程类似,但是在commitMutationEffectsOnFiber的实现中有如下代码:
function commitMutationEffectsOnFiber(finishedWork,root,lanes){
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
// DFS 递归子树
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
if (flags & Update) {
commitHookEffectListUnmount(
HookInsertion | HookHasEffect,
finishedWork,
finishedWork.return,
);
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
commitHookLayoutUnmountEffects(
finishedWork,
finishedWork.return,
HookLayout | HookHasEffect,
);
}
break;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在上述阶段中,先进行useInsertionEffect副作用的清理,再执行useInserttionEffect副作用,然后清理useLayout副作用清理。
# 3.4 useEffect/useLayoutEffect/useInsertionEffect调度
这三者副作用的清理(destroy)和执行(create)最后的实现是相同的逻辑,不过就是各自的tag不同,执行的时机不同。
| Hook | 执行时机 | 阶段 | 是否阻塞渲染 |
|---|---|---|---|
| useInsertionEffect | 最早 | mutation | ✅ 阻塞 |
| useLayoutEffect | commit 后 | layout | ✅ 阻塞 |
| useEffect | commit 后异步 | passive | ❌ 不阻塞 (scheduler调度) |
# 通用方法
# mountWorkInProgressHook方法
mountWorkInProgress方法用于获取创建副作用的hook,并返回当前的hook,即workInProgress。
function mountWorkInProgressHook(){
const hook = {
memoizedState: null, // 保存hook
baseState: null, //
baseQueue: null,
queue: null,
next: null, // 指向下一个hook
}
// 判断当前hooks是否为null,
// 若在mount阶段,当前副作用是第一个,在其为null,否则为上一个副作用
if(workInProgressHook === null){
// 将组件内的第一个hook保存到当前fiber的memoizedState上
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
}else{
// 将新建的hook链接到当前hook的next上,构成一个单向链表
// 将新建的hook赋值给当前hook
workInProgressHook = workInProgressHook.next = hook
}
// 返回当前hook,即新建的hook
return workInProgressHook;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# updateWorkInProgressHook方法
updateWorkInProgressHook方法在函数组件更新阶段会被调用,其作用就是:
- 找到上一次渲染的旧hook
- 找到/创建本次渲染的新hook
- 将新旧hook关联起来,一一对应,复用旧数据,串联成新的hook链表
- 返回当前处理的新hook
function updateWorkInProgressHook() {
let nextCurrentHook; //表示下一个旧 Hook
// 判断currentHook是否为null,currentHook指向旧 Fiber 节点上的当前 Hook(组件上一次渲染的 Hook)
if (currentHook === null) {
// 拿到旧的Fiber节点 当前fiber的alternate指向旧的Fiber节点
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
// 旧Fiber节点存在,则第一个旧hook就是memoizedState
nextCurrentHook = current.memoizedState;
} else {
// 没有旧Fiber节点,首次渲染
nextCurrentHook = null;
}
} else {
// 不是第一个hook,则通过当前旧 hook的next当前hook的旧 hook
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook;// 表示下一个新hook
// 当前 hook是null,说明是第一个hook
if (workInProgressHook === null) {
// 将当前Fiber的memoizedState赋值给下一个新hook
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
// 当前hook不为null,则通过next拿到下一个新hook
nextWorkInProgressHook = workInProgressHook.next;
}
// 若下一个新hook不为null,则直接复用
if (nextWorkInProgressHook !== null) {
// 将当前新 hook指向下一个新hook
workInProgressHook = nextWorkInProgressHook;
// 将下一个新hook指向下下个新hook
nextWorkInProgressHook = workInProgressHook.next;
// 同步移动旧 hook的指针
currentHook = nextCurrentHook;
} else {
// 若旧hook不存在,则说明hooks调用顺序乱了
if (nextCurrentHook === null) {
const currentFiber = currentlyRenderingFiber.alternate;
if (currentFiber === null) {
// 首次渲染没有旧hook报错
} else {
// 更新阶段 hooks数量/顺序变了报错 可能是条件调用hook,这会被react禁止
}
}
// 移动旧hook
currentHook = nextCurrentHook;
// 基于旧hook 创建全新的hook
const newHook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
// 判断当前 hook是否是第一个,第一个则为null
if (workInProgressHook === null) {
// 将新 hook挂载到当前Fiber的根 hook,并且将当前hook的指向新hook
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// 非第一个hook,将新hook追加到链表尾,并且将其赋值给当前hook
workInProgressHook = workInProgressHook.next = newHook;
}
}
// 返回当前hook
return workInProgressHook;
}
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74