源码
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 渲染。