Appearance
Passive Effects 阶段详解
这个阶段与 Layout Effects 不同,它的执行是异步的。这意味着 React 会在完成 DOM 突变和 Layout Effects 之后,允许浏览器先进行绘制(paint),然后再回来执行这些 Passive Effects。这样做的好处是它们不会阻塞浏览器的渲染,从而可以提升用户感知的性能。
核心特点:
- 异步执行:这是 Passive Effects 与 Layout Effects 最显著的区别。Passive Effects 会在浏览器完成绘制(Paint)之后异步执行。这意味着它们不会阻塞浏览器的渲染流程,从而有助于提升用户感知的性能,特别适合数据获取、设置订阅、手动操作非 React 管理的 DOM(且不需要同步时)等操作。
- 清理机制:
useEffect
Hook 返回的清理函数(cleanup function)也是作为 Passive Effect 的一部分执行的。这个清理函数会在以下两种情况执行:- 组件卸载前。
- 在组件更新时,如果
useEffect
的依赖项发生变化,导致 effect 需要重新执行,那么在执行新的 effect 之前,会先执行上一次 effect 返回的清理函数。
- 顺序保证:
- 在一个组件内部,多个
useEffect
的执行顺序与它们在代码中定义的顺序一致。 - 清理函数的执行顺序则与定义的顺序相反(即最后一个定义的
useEffect
的清理函数最先执行)。
- 在一个组件内部,多个
执行流程:
Passive Effects 的执行主要由一个名为 flushPassiveEffects
(或类似名称) 的函数来调度和管理。这个过程通常在 Commit 阶段的 Layout Effects 执行完毕后启动。
调度 (
schedulePassiveEffects
):- 当 Layout Effects 阶段完成后,React 会检查是否存在待处理的 Passive Effects(即 Fiber 节点上是否有
Passive
标记)。 - 如果存在,React 会使用其内部的调度器以较低的优先级异步调度
flushPassiveEffects
函数的执行。这确保了浏览器有时间完成当前的绘制工作。
- 当 Layout Effects 阶段完成后,React 会检查是否存在待处理的 Passive Effects(即 Fiber 节点上是否有
核心实现 (
flushPassiveEffectsImpl
或flushPassiveEffects
): 一旦调度器决定执行该任务,flushPassiveEffects
函数会被调用。其核心逻辑分为两个主要步骤(或循环),以确保正确的执行顺序:第一步:执行销毁 (Unmount/Cleanup) 副作用:
- React 会遍历所有标记了需要执行 Passive Effect 清理的 Fiber 节点。
- 对于函数组件,这对应于执行
useEffect
Hook 在上一次渲染时返回的清理函数。 - 这个过程确保了在执行新的创建副作用之前,所有旧的、相关的副作用已经被妥善清理。
- 清理函数执行后,其引用通常会被置空。
第二步:执行创建 (Mount/Create) 副作用:
- 在所有必要的清理函数都执行完毕后,React 会遍历所有标记了需要执行 Passive Effect 创建的 Fiber 节点。
- 对于函数组件,这对应于执行
useEffect
Hook 的回调函数(即传递给useEffect
的第一个参数)。 - 该回调函数返回的结果(如果是一个函数)会被保存为下一次需要执行的清理函数。
递归处理:
- 在处理单个 Fiber 节点的 Passive Effects 时(无论是 Mount 还是 Unmount),通常会先递归处理其子孙节点的同类 Passive Effects,然后再处理当前节点。这意味着 Passive Effects 的实际执行顺序(无论是创建还是销毁)在组件树中通常是自下而上(子节点先于父节点,对于 effect 本身)然后是兄弟节点之间按定义顺序。
错误处理:
- 如果在执行 Passive Effects(包括清理函数和创建函数)期间发生错误,React 通常会捕获这些错误并记录下来,但一般不会像同步阶段的错误那样中断整个应用的运行或触发最近的错误边界。这是因为它们是异步执行的,对渲染一致性的直接影响较小。
状态管理:
- React 内部会维护状态(如
isFlushingPassiveEffects
)来避免flushPassiveEffects
的重入,并管理待处理的 Passive Effects 队列。
- React 内部会维护状态(如
流程图
涉及的主要函数
schedulePassiveEffects
: 负责在 Layout Effects 之后调度flushPassiveEffects
。flushPassiveEffects
/flushPassiveEffectsImpl
: 核心执行函数,管理销毁和创建两个循环。commitPassiveUnmountEffectsOnFiber
(或类似逻辑): 处理单个 Fiber 节点的useEffect
清理。- 内部会查找
updateQueue
中的effect
链表,找到带有Passive
和HasEffect
标记的 effect,并执行其destroy
方法。
- 内部会查找
commitPassiveMountEffectsOnFiber
(或类似逻辑): 处理单个 Fiber 节点的useEffect
创建。- 内部会查找
updateQueue
中的effect
链表,找到带有Passive
标记的 effect,执行其create
方法,并将返回值存为新的destroy
方法。
- 内部会查找
recursivelyTraversePassiveUnmountEffects
/recursivelyTraversePassiveMountEffects
: 用于递归遍历子树以确保正确的执行顺序。
总结来说,Passive Effects 提供了一种在不阻塞浏览器渲染的前提下执行副作用的机制。通过异步调度和明确的清理/创建两阶段执行,React 确保了 useEffect
的行为既高效又符合开发者的预期,是现代 React 应用中进行副作用管理的核心部分。
