源码
useEffect 是 React 中的一个核心 Hook,用于在函数组件中处理副作用(side effects)。副作用包括数据获取、订阅、手动 DOM 操作以及记录日志等操作
。以下是对 useEffect 的源码和底层原理的解析。
源码位置:github.com/facebook/re...
useEffect 的实现分为两个部分: mountEffect
和 updateEffect
,分别对应组件的挂载阶段
和更新阶段
。
挂载阶段:mountEffect
mountEffect
是在组件首次渲染时调用的,用于注册副作用。
ts
/**
* 处理 useEffect 的挂载逻辑,负责创建、更新和销毁副作用的调度
*
* @param create - 副作用创建函数(对应 useEffect 的第一个参数)
* @param createDeps - 创建阶段的依赖数组(对应 useEffect 的第二个参数)
* @param update - (可选)副作用更新回调,用于 CRUD 重载模式
* @param updateDeps - (可选)更新阶段的依赖数组
* @param destroy - (可选)副作用销毁回调,用于 CRUD 重载模式
*/
function mountEffect(
create: (() => (() => void) | void) | (() => {...} | void | null),
createDeps: Array<mixed> | void | null,
update?: ((resource: {...} | void | null) => void) | void,
updateDeps?: Array<mixed> | void | null,
destroy?: ((resource: {...} | void | null) => void) | void,
): void {
// 开发环境下且处于严格模式时,启用增强型副作用检查
// `__DEV__` 标志区分开发/生产环境行为
// `currentlyRenderingFiber.mode` 判断并发模式特性
if (
__DEV__ &&
(currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode &&
(currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode
) {
// 判断是否启用 CRUD 重载模式(增删改查扩展)
// `enableUseEffectCRUDOverload` 实验性功能开关
// 当检测到 `update` 或 `destroy` 参数存在时,启用资源生命周期管理
if (
enableUseEffectCRUDOverload &&
(typeof update === 'function' || typeof destroy === 'function')
) {
// 挂载支持资源管理的增强型副作用(带更新/销毁回调)
mountResourceEffectImpl(
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, // 组合标志位:
// - MountPassiveDevEffect: 开发模式下的被动效果挂载(专用的挂载追踪)
// - PassiveEffect: 标准被动效果(useEffect 类型)
// - PassiveStaticEffect: 静态树优化相关(防止不必要的副作用触发)
HookPassive, // 标识这是 useEffect 类型(区别于 useLayoutEffect)
create,
createDeps,
update,
updateDeps,
destroy,
);
} else {
// 挂载标准副作用(无 CRUD 扩展)
mountEffectImpl(
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
createDeps,
);
}
} else {
// 生产环境或非严格模式逻辑
// 重点看这里
if (
enableUseEffectCRUDOverload &&
(typeof update === 'function' || typeof destroy === 'function')
) {
// 生产环境下的资源管理副作用(移除开发模式专用标志位)
mountResourceEffectImpl(
PassiveEffect | PassiveStaticEffect, // 生产环境不包含 MountPassiveDevEffect
HookPassive,
create,
createDeps,
update,
updateDeps,
destroy,
);
} else {
// 标准生产环境副作用处理
// 我们大部分使用的时候,执行的这里
// 核心逻辑被委托给 `mountEffectImpl`,它会将副作用注册到当前 Fiber 节点的更新队列中。
mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
createDeps,
);
}
}
}
核心分流逻辑
更新阶段:updateEffect
updateEffect
是在组件更新时调用的,用于检查依赖是否发生变化,并决定是否重新执行副作用。
ts
/**
* 处理 useEffect 在组件更新阶段的副作用逻辑,根据是否启用 CRUD 扩展模式选择不同的更新策略
*
* @param create - 副作用创建函数(对应 useEffect 的第一个参数)
* @param createDeps - 创建阶段的依赖数组(对应 useEffect 的第二个参数)
* @param update - (可选)副作用更新回调,用于 CRUD 重载模式
* @param updateDeps - (可选)更新阶段的依赖数组
* @param destroy - (可选)副作用销毁回调,用于 CRUD 重载模式
*/
function updateEffect(
create: (() => (() => void) | void) | (() => {...} | void | null),
createDeps: Array<mixed> | void | null,
update?: ((resource: {...} | void | null) => void) | void,
updateDeps?: Array<mixed> | void | null,
destroy?: ((resource: {...} | void | null) => void) | void,
): void {
// 判断是否启用 CRUD 扩展模式且存在更新/销毁回调
if (
enableUseEffectCRUDOverload && // 实验性功能开关
(typeof update === 'function' || typeof destroy === 'function')
) {
// 执行带资源管理的增强型副作用更新
updateResourceEffectImpl(
PassiveEffect, // 标志位:标准被动效果(对应 useEffect)
HookPassive, // Hook 类型标识(区别于 useLayoutEffect)
create, // 原始创建函数
createDeps, // 创建阶段依赖
update, // 更新回调(当依赖变化时触发)
updateDeps, // 更新阶段依赖
destroy // 资源销毁回调
);
} else {
// 执行标准副作用更新流程
updateEffectImpl(
PassiveEffect, // 副作用类型标识
HookPassive, // Hook 类型标识
create, // 副作用创建函数
createDeps // 依赖数组
);
}
}
核心逻辑解析
- CRUD 扩展模式 (
enableUseEffectCRUDOverload
)
-
性质:实验性功能
-
功能特性 :
- 允许更精细控制副作用的生命周期
- 通过
update/destroy
回调实现资源管理
-
典型应用场景 :
js// WebSocket 连接管理示例 useEffect({ create: () => new WebSocket(url), update: (socket) => socket.reconnect(), destroy: (socket) => socket.close(), }, [url])
- 更新策略分流机制
- 增强模式路径 →
updateResourceEffectImpl
- 标准模式路径 → `updateEffectImpl
核心实现:mountEffectImpl
和 updateEffectImpl
这两个函数是 useEffect
的核心实现,负责将副作用注册到 Fiber 节点的更新队列中。
ts
/**
* 处理 useEffect 在组件挂载阶段的初始化逻辑(React 内部实现)
*
* @param fiberFlags - Fiber 节点标记(如 PassiveEffect 表示异步副作用)
* @param hookFlags - Hook 类型标记(如 HookPassive 对应 useEffect)
* @param create - 用户传入的副作用函数
* @param createDeps - 依赖项数组
*/
function mountEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
createDeps: Array<mixed> | void | null,
): void {
// 1. 创建新的 Hook 节点并挂载到 Fiber 链表
const hook = mountWorkInProgressHook();
// 2. 规范化依赖项(将 undefined 转换为 null)
const nextDeps = createDeps === undefined ? null : createDeps;
// 3. 标记 Fiber 节点需要处理副作用(如 PassiveEffect 会触发异步调度)
currentlyRenderingFiber.flags |= fiberFlags;
// 4. 创建副作用对象并存入 Hook 状态
hook.memoizedState = pushSimpleEffect(
HookHasEffect | hookFlags, // 组合标志位:标记需要执行的副作用
createEffectInstance(), // 创建空的副作用实例容器
create, // 用户传入的副作用函数
nextDeps, // 规范化后的依赖数组
);
}
/**
* 处理 useEffect 在组件更新阶段的更新逻辑(React 内部实现)
*
* @param fiberFlags - Fiber 节点标记
* @param hookFlags - Hook 类型标记
* @param create - 新的副作用函数
* @param deps - 新的依赖项数组
*/
function updateEffectImpl(
fiberFlags: Flags,
hookFlags: HookFlags,
create: () => (() => void) | void,
deps: Array<mixed> | void | null,
): void {
// 1. 获取当前正在处理的 Hook
const hook = updateWorkInProgressHook();
// 2. 规范化新依赖项
const nextDeps = deps === undefined ? null : deps;
// 3. 获取上一次渲染的副作用状态
const effect: Effect = hook.memoizedState;
const inst = effect.inst; // 关联的副作用实例(存储清理函数等)
// 4. 依赖对比逻辑(仅在非首次渲染时执行)
if (currentHook !== null) {
if (nextDeps !== null) {
// 4.1 获取上一次的依赖项
const prevEffect: Effect = currentHook.memoizedState;
const prevDeps = prevEffect.deps;
// 4.2 依赖项未变化时优化处理
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 复用现有副作用(不添加 HookHasEffect 标记)
hook.memoizedState = pushSimpleEffect(
hookFlags, // 仅保留基础标记
inst, // 复用实例(避免重复创建)
create, // 理论上可复用但 React 会传入新函数
nextDeps,
);
return; // 提前退出,跳过副作用更新
}
}
}
// 5. 依赖变化时的处理
// 5.1 标记 Fiber 需要处理副作用
currentlyRenderingFiber.flags |= fiberFlags;
// 5.2 创建新的副作用记录(添加 HookHasEffect 标记)
hook.memoizedState = pushSimpleEffect(
HookHasEffect | hookFlags, // 标记需要执行副作用
inst, // 可能复用或替换实例
create, // 新传入的副作用函数
nextDeps,
);
}
/**
* 核心逻辑说明:
*
* 1. 标志位系统:
* - HookHasEffect:核心标记,决定是否执行副作用
* - fiberFlags:控制副作用调度时机(如 PassiveEffect 表示异步执行)
* - hookFlags:标识 Hook 类型(Layout/Passive)
*
* 2. 副作用生命周期:
* 挂载阶段:创建 -> 渲染后执行
* 更新阶段:依赖变化 -> 清理旧 -> 执行新
* 卸载阶段:执行清理
*
* 3. 性能优化:
* - 依赖未变化时跳过 HookHasEffect 标记
* - 复用 effect 实例避免重复创建
* - 通过位运算快速组合/检查标记
*
* 4. 关键函数说明:
* - areHookInputsEqual:深度对比依赖项数组
* - createEffectInstance:创建存储清理函数等元数据的容器
* - pushSimpleEffect:创建并返回 effect 对象
*/
底层原理
-
Fiber 数据结构:
- 每个组件对应一个 Fiber 节点,
useEffect
的副作用会存储在 Fiber 节点的updateQueue
中。 updateQueue
是一个链表,存储了所有的副作用。
- 每个组件对应一个 Fiber 节点,
-
依赖追踪:
- React 会在每次渲染时比较依赖数组,只有当依赖发生变化时才会重新执行副作用。
- 这种机制可以避免不必要的副作用执行,提高性能。
-
清理机制:
- 如果副作用返回一个清理函数,React 会在下一次执行副作用之前调用它。
- 清理函数的调用时机包括组件卸载和依赖变化。
-
调度与优先级:
useEffect
的副作用是异步执行的,React 会在浏览器空闲时执行它们。- 通过设置
fiberFlags
,React 可以在提交阶段标记需要执行的副作用。
总结
useEffect 是 React 用于管理副作用的 Hook,它在 commit 阶段 统一执行,确保副作用不会影响渲染。
在 React 源码中,useEffect 通过 Fiber 机制 在 commit 阶段 进行处理:
(1) useEffect 存储在 Fiber 节点上
React 组件是通过 Fiber 数据结构 组织的,每个 useEffect 都会存储在 fiber.updateQueue 中。
(2) useEffect 何时执行
React 组件更新后,React 在 commit 阶段 统一遍历 effect 队列,并执行 useEffect 副作用。
React 使用 useEffectEvent()
注册 effect,在 commitLayoutEffect 之后,异步执行 useEffect,避免阻塞 UI 渲染。
(3) useEffect 依赖变化的处理
依赖数组的比较使用 Object.is()
,只有依赖变化时才重新执行 useEffect。
在更新阶段,React 遍历旧 effect,并先执行清理函数,然后再执行新的 effect。
简化的 useEffect 实现如下:
js
function useEffect(callback, dependencies) {
const currentEffect = getCurrentEffect() // 获取当前 Fiber 节点的 Effect
if (dependenciesChanged(currentEffect.dependencies, dependencies)) {
cleanupPreviousEffect(currentEffect) // 先执行上次 effect 的清理函数
const cleanup = callback() // 执行 useEffect 传入的回调
currentEffect.dependencies = dependencies
currentEffect.cleanup = cleanup // 存储清理函数
}
}
相比 useLayoutEffect,useEffect 是 异步执行,不会阻塞 UI 渲染。