网站首页 > 技术文章 正文
获课:keyouit.xyz/5247/
在 React 18 中,组件卸载时确实会触发 useEffect 的清理函数。这一行为是 React 副作用机制的核心设计之一,其实现涉及 effect 链表管理、依赖追踪以及调度系统的协作。以下从源码级视角深入解析其机制:
一、useEffect清理函数的触发时机
- 组件卸载时
当组件从 DOM 中移除(如父组件更新导致子组件销毁、路由切换等),React 会调用 commitHookEffectListUnmount 函数,遍历当前组件所有 effect 链表,并依次执行其清理函数(destroy 函数)。 - 依赖项变更导致的重新执行前
若组件未卸载,但 useEffect 的依赖项发生变化,React 会先执行旧 effect 的清理函数,再执行新 effect 的挂载逻辑(通过 commitHookEffectListMount)。
二、源码级实现细节
1.effect链表的结构
React 通过 Fiber 节点维护 effect 链表,每个 effect 对象包含以下关键字段:
typescript
interface Effect { | |
tag: HookType; // 标识是 useEffect、useLayoutEffect 等 | |
create: () => (() => void) | void; // 副作用函数 | |
destroy: (() => void) | void; // 清理函数 | |
deps: DependencyList | null; // 依赖项数组 | |
next: Effect | null; // 指向链表中的下一个 effect | |
} |
2. 清理函数的注册与执行
- 注册阶段
在 renderWithHooks 函数中,React 根据 useEffect 的参数生成 effect 对象,并将其挂载到当前组件的 Fiber 节点的 updateQueue 中。 - 提交阶段(Commit Phase)
在 commitRootImpl 函数中,React 根据操作类型(挂载/更新/卸载)调用不同的处理函数: - 卸载:commitHookEffectListUnmount
遍历 Fiber 节点的 effect 链表,依次执行每个 effect 的 destroy 函数。 - 挂载/更新:commitHookEffectListMount
执行 create 函数生成新的副作用,并保存其返回的清理函数到 destroy 字段。
3. 依赖项追踪与比较
React 通过 areHookInputsEqual 函数比较新旧依赖项数组,决定是否需要重新执行副作用:
typescript
function areHookInputsEqual(nextDeps: DependencyList, prevDeps: DependencyList | null) { | |
if (prevDeps === null) { | |
return false; | |
} | |
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { | |
if (Object.is(nextDeps[i], prevDeps[i])) { | |
continue; | |
} | |
return false; | |
} | |
return true; | |
} |
三、关键源码路径分析
- 卸载时的清理函数调用
在 commitHookEffectListUnmount 中: - typescript
- function commitHookEffectListUnmount(
- tag: number,
- finishWork: Fiber,
- nearestMountedAncestor: Fiber | null,
- ) {
- const updateQueue: FunctionComponentUpdateQueue | null = (finishWork.updateQueue: any);
- if (updateQueue !== null) {
- const lastEffect = updateQueue.lastEffect;
- if (lastEffect !== null) {
- let firstEffect = lastEffect.next;
- let effect = firstEffect;
- do {
- if ((effect.tag & tag) === tag) {
- // 执行清理函数
- const destroy = effect.destroy;
- effect.destroy = undefined;
- if (destroy !== undefined) {
- destroy();
- }
- }
- effect = effect.next;
- } while (effect !== firstEffect);
- }
- }
- }
- 依赖项变更时的清理逻辑
在 commitHookEffectListMount 中: - typescript
- function commitHookEffectListMount(
- tag: number,
- finishWork: Fiber,
- nearestMountedAncestor: Fiber | null,
- ) {
- // ... 省略其他逻辑
- const create = effect.create;
- effect.destroy = create(); // 保存清理函数
- }
四、为什么需要清理函数?
- 资源释放
例如取消定时器、网络请求、事件监听等,避免内存泄漏。 - 状态同步
如第三方库的初始化/销毁(如 echarts 实例的销毁)。 - 副作用隔离
确保副作用仅在组件挂载期间生效。
五、示例验证
jsx
import { useEffect } from 'react'; | |
function Example() { | |
useEffect(() => { | |
const timer = setInterval(() => console.log('Tick'), 1000); | |
return () => { | |
console.log('Cleanup'); | |
clearInterval(timer); | |
}; | |
}, []); | |
return <div>Example</div>; | |
} | |
// 卸载时输出: "Cleanup" |
六、总结
- 组件卸载时,React 通过 commitHookEffectListUnmount 遍历 effect 链表并执行清理函数。
- 依赖项变更时,React 先执行旧 effect 的清理函数,再执行新 effect 的挂载逻辑。
- 这一机制由 Fiber 节点、updateQueue 和调度系统协作完成,确保副作用的生命周期与组件严格同步。
通过理解 effect 链表的管理和依赖追踪的实现,可以更高效地编写副作用逻辑,避免潜在的资源泄漏问题。
猜你喜欢
- 2025-05-23 React 18:新玩具、新陷阱以及新可能性
- 2025-05-23 第395期Web技术日报(2016-01-22) 使用 React 的一些经验
- 2025-05-23 React 组件渲染慢到崩溃?5 大实战技巧让页面流畅度飙升 80%!
- 2025-05-23 React 入门:从 JavaScript 到 React
- 2025-05-23 01 React入门
- 2025-05-23 突发!React官方正式弃用CRA!Next.js、Remix、Vite迁移指南
- 2025-05-23 用JavaScript开发移动原生应用,Facebook正式开源React Native!
- 2025-05-23 性能焦虑!前端人必看!5 个 React 组件优化神技! 颠覆你的认知!
- 2025-05-23 开始学习React - 概览和演示教程
- 2024-09-22 「连载二」「环境搭建」前端框架 React 入门教程
你 发表评论:
欢迎- 05-23浅谈3种css技巧——两端对齐
- 05-23JSONP安全攻防技术
- 05-23html5学得好不好,看掌握多少标签
- 05-23Chrome 调试时行号错乱
- 05-23本文帮你在Unix上玩转C语言
- 05-23Go 中的安全编码 - 输入验证
- 05-2331个必备的python字符串方法,建议收藏
- 05-23Dynamics.js – 创建逼真的物理动画的 JS 库
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端md5加密 (49)
- 前端路由 (55)
- 前端数组 (65)
- 前端定时器 (47)
- 前端接口 (46)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle约束 (46)
- oracle 中文 (51)
- oracle链接 (47)
- oracle的函数 (57)
- mac oracle (47)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)