Skip to content

Passive Effects 阶段详解

这个阶段与 Layout Effects 不同,它的执行是异步的。这意味着 React 会在完成 DOM 突变和 Layout Effects 之后,允许浏览器先进行绘制(paint),然后再回来执行这些 Passive Effects。这样做的好处是它们不会阻塞浏览器的渲染,从而可以提升用户感知的性能。

核心特点:

  1. 异步执行:这是 Passive Effects 与 Layout Effects 最显著的区别。Passive Effects 会在浏览器完成绘制(Paint)之后异步执行。这意味着它们不会阻塞浏览器的渲染流程,从而有助于提升用户感知的性能,特别适合数据获取、设置订阅、手动操作非 React 管理的 DOM(且不需要同步时)等操作。
  2. 清理机制useEffect Hook 返回的清理函数(cleanup function)也是作为 Passive Effect 的一部分执行的。这个清理函数会在以下两种情况执行:
    • 组件卸载前。
    • 在组件更新时,如果 useEffect 的依赖项发生变化,导致 effect 需要重新执行,那么在执行新的 effect 之前,会先执行上一次 effect 返回的清理函数。
  3. 顺序保证
    • 在一个组件内部,多个 useEffect 的执行顺序与它们在代码中定义的顺序一致。
    • 清理函数的执行顺序则与定义的顺序相反(即最后一个定义的 useEffect 的清理函数最先执行)。

执行流程:

Passive Effects 的执行主要由一个名为 flushPassiveEffects (或类似名称) 的函数来调度和管理。这个过程通常在 Commit 阶段的 Layout Effects 执行完毕后启动。

  1. 调度 (schedulePassiveEffects)

    • 当 Layout Effects 阶段完成后,React 会检查是否存在待处理的 Passive Effects(即 Fiber 节点上是否有 Passive 标记)。
    • 如果存在,React 会使用其内部的调度器以较低的优先级异步调度 flushPassiveEffects 函数的执行。这确保了浏览器有时间完成当前的绘制工作。
  2. 核心实现 (flushPassiveEffectsImplflushPassiveEffects): 一旦调度器决定执行该任务,flushPassiveEffects 函数会被调用。其核心逻辑分为两个主要步骤(或循环),以确保正确的执行顺序:

    • 第一步:执行销毁 (Unmount/Cleanup) 副作用

      • React 会遍历所有标记了需要执行 Passive Effect 清理的 Fiber 节点。
      • 对于函数组件,这对应于执行 useEffect Hook 在上一次渲染时返回的清理函数。
      • 这个过程确保了在执行新的创建副作用之前,所有旧的、相关的副作用已经被妥善清理。
      • 清理函数执行后,其引用通常会被置空。
    • 第二步:执行创建 (Mount/Create) 副作用

      • 在所有必要的清理函数都执行完毕后,React 会遍历所有标记了需要执行 Passive Effect 创建的 Fiber 节点。
      • 对于函数组件,这对应于执行 useEffect Hook 的回调函数(即传递给 useEffect 的第一个参数)。
      • 该回调函数返回的结果(如果是一个函数)会被保存为下一次需要执行的清理函数。
  3. 递归处理

    • 在处理单个 Fiber 节点的 Passive Effects 时(无论是 Mount 还是 Unmount),通常会先递归处理其子孙节点的同类 Passive Effects,然后再处理当前节点。这意味着 Passive Effects 的实际执行顺序(无论是创建还是销毁)在组件树中通常是自下而上(子节点先于父节点,对于 effect 本身)然后是兄弟节点之间按定义顺序。
  4. 错误处理

    • 如果在执行 Passive Effects(包括清理函数和创建函数)期间发生错误,React 通常会捕获这些错误并记录下来,但一般不会像同步阶段的错误那样中断整个应用的运行或触发最近的错误边界。这是因为它们是异步执行的,对渲染一致性的直接影响较小。
  5. 状态管理

    • React 内部会维护状态(如 isFlushingPassiveEffects)来避免 flushPassiveEffects 的重入,并管理待处理的 Passive Effects 队列。

流程图

涉及的主要函数

  • schedulePassiveEffects: 负责在 Layout Effects 之后调度 flushPassiveEffects
  • flushPassiveEffects / flushPassiveEffectsImpl: 核心执行函数,管理销毁和创建两个循环。
  • commitPassiveUnmountEffectsOnFiber (或类似逻辑): 处理单个 Fiber 节点的 useEffect 清理。
    • 内部会查找 updateQueue 中的 effect 链表,找到带有 PassiveHasEffect 标记的 effect,并执行其 destroy 方法。
  • commitPassiveMountEffectsOnFiber (或类似逻辑): 处理单个 Fiber 节点的 useEffect 创建。
    • 内部会查找 updateQueue 中的 effect 链表,找到带有 Passive 标记的 effect,执行其 create 方法,并将返回值存为新的 destroy 方法。
  • recursivelyTraversePassiveUnmountEffects / recursivelyTraversePassiveMountEffects: 用于递归遍历子树以确保正确的执行顺序。

总结来说,Passive Effects 提供了一种在不阻塞浏览器渲染的前提下执行副作用的机制。通过异步调度和明确的清理/创建两阶段执行,React 确保了 useEffect 的行为既高效又符合开发者的预期,是现代 React 应用中进行副作用管理的核心部分。


微信公众号二维码