Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 《React源码》笔记
  • React-reconciler
  • hooks
东流
2026-03-29
目录

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
1
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) // 依赖数组
}
1
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,
    )
}
1
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
}
1
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"]
1
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);
}
1
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);
}
1
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
1
2
3
4
5
6

PassiveMask掩码与Passive的关系如下:

const PassiveMask = Passive | ChildDeletion
1

即在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
1
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;
}
1
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
1
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;
    }
} 
1
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;
    }
  }
}
1
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) {
    // 报错
  }
}
1
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
1
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,
                );
            }
        }
    }
}
1
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;
        }
    }
}
1
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 () {
    // < !--报错-->
}

}
1
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;
    }
  }    
}
1
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);
}
1
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;
    }
}
1
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;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# updateWorkInProgressHook方法

updateWorkInProgressHook方法在函数组件更新阶段会被调用,其作用就是:

  1. 找到上一次渲染的旧hook
  2. 找到/创建本次渲染的新hook
  3. 将新旧hook关联起来,一一对应,复用旧数据,串联成新的hook链表
  4. 返回当前处理的新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;
}
1
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
编辑 (opens new window)
上次更新: 2026/03/29, 13:41:34
最近更新
01
会动的png
03-24
02
React19 useOptimistic hooks介绍
03-19
03
completeWork方法解析
03-11
更多文章>
Theme by Vdoing | Copyright © 2024-2026 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式