当前位置: 首页 > news >正文

【大前端】React useEffect 详解:从入门到进阶

React useEffect 详解:从入门到进阶

在 React Hooks 出现之前,我们经常通过 生命周期函数componentDidMountcomponentDidUpdatecomponentWillUnmount 等)来管理副作用逻辑。
React 16.8 起,Hooks 引入了 useEffect,统一了副作用处理逻辑,使函数组件具备了管理副作用的能力。


1. 什么是副作用(Side Effect)?

在 React 中,副作用 指的是那些会影响函数组件之外环境的操作,比如:

  • 数据请求(Ajax / fetch / axios)
  • DOM 操作(手动修改元素属性)
  • 订阅/取消订阅(WebSocket、事件监听器)
  • 定时器(setIntervalsetTimeout

这些逻辑如果直接写在函数体里,会导致 多次执行、状态不一致 等问题,因此 React 提供了 useEffect 来专门管理副作用。


2. useEffect 基本语法

useEffect(() => {// 副作用逻辑return () => {// 清理逻辑(可选)};
}, [依赖项]);

参数解析

  • 第一个参数:一个函数,包含副作用逻辑。

    • 可返回一个清理函数,用于组件卸载或依赖变化时执行清理。
  • 第二个参数:依赖数组(dependency array)。

    • [] 空数组 → 仅在初次渲染执行一次(类似 componentDidMount)。
    • [state, props] → 当依赖项变化时执行(类似 componentDidUpdate)。
    • 省略 → 每次渲染后都执行。

3. 使用场景举例

3.1 模拟 componentDidMount

useEffect(() => {console.log("组件挂载完成");
}, []);

3.2 模拟 componentDidUpdate

useEffect(() => {console.log("count 变化了:", count);
}, [count]);

3.3 模拟 componentWillUnmount

useEffect(() => {const id = setInterval(() => console.log("定时器"), 1000);return () => clearInterval(id); // 清理逻辑
}, []);

4. 常见坑点

4.1 依赖数组遗漏

useEffect(() => {fetch(`/api/user/${id}`);
}, []); 

❌ 错误:id 变化时不会重新请求
✅ 正确:

useEffect(() => {fetch(`/api/user/${id}`);
}, [id]);

4.2 无限循环陷阱

useEffect(() => {setCount(count + 1); // 修改状态
}, [count]);

⚠️ 会导致无限循环更新。
👉 解决:需要加条件判断,或用 useRef 缓存不影响渲染的变量。


4.3 异步函数处理

useEffect 不能直接传入 async 函数:

// ❌ 错误写法
useEffect(async () => {const res = await fetchData();
}, []);

✅ 正确写法:

useEffect(() => {async function loadData() {const res = await fetchData();setData(res);}loadData();
}, []);

5. useEffect vs useLayoutEffect

  • useEffect:异步执行,不会阻塞浏览器渲染(大多数场景使用它)。
  • useLayoutEffect:同步执行,DOM 更新后立即运行,常用于需要精确操作 DOM 的场景。

6. 最佳实践

  1. 依赖项必须写全:避免因闭包导致取到旧值。
  2. 拆分 effect:不同逻辑不要写在一个 useEffect,保证单一职责。
  3. 避免频繁触发:对频繁变化的值,可以结合 useDebounceuseThrottle
  4. 合理使用清理函数:确保事件监听、定时器等不会造成内存泄漏。

7. 总结

  • useEffect 是 React 用来管理副作用的统一接口。
  • 依赖数组的使用是关键,决定了副作用的执行时机。
  • 清理函数是避免内存泄漏的利器。
  • 面试常问陷阱:依赖数组遗漏无限循环更新异步写法错误

📌 推荐一个思考题(常见面试题):
为什么下面的 useEffect 打印的总是旧的 count

useEffect(() => {const id = setInterval(() => {console.log(count);}, 1000);return () => clearInterval(id);
}, []);

这是一个典型的 闭包陷阱 (stale closure) 问题。我们来拆解一下原因和解决办法:


🔍 原因

你的代码:

useEffect(() => {const id = setInterval(() => {console.log(count);}, 1000);return () => clearInterval(id);
}, []);

关键点:

  • useEffect 的依赖数组是 [],所以只会在组件挂载时执行一次。
  • setInterval 回调函数创建时,它“捕获”了当时的 count 变量。
  • 之后即使 count 在组件中更新了,回调函数里的 count 依旧是老的值,因为闭包绑定的是初始快照。

所以 console.log(count) 打印的总是旧的。


✅ 解决办法

方法 1:把 count 放到依赖数组里

useEffect(() => {const id = setInterval(() => {console.log(count);}, 1000);return () => clearInterval(id);
}, [count]);

这样,每次 count 更新时,useEffect 会重新执行,重新注册一个新的定时器,拿到最新的 count

⚠️ 但这种方式会不断清除和重建定时器,有时不太优雅。


方法 2:使用函数式更新 + useRef

利用 useRef 保存最新的 count

const countRef = useRef(count);useEffect(() => {countRef.current = count;
}, [count]);useEffect(() => {const id = setInterval(() => {console.log(countRef.current);}, 1000);return () => clearInterval(id);
}, []);

这里定时器只建立一次,但回调里取的是 countRef.current,它会随着 count 更新而变化。


方法 3:函数式 setState

如果逻辑允许,可以用函数式更新避免依赖旧状态:

setCount(prev => prev + 1);

这样不依赖闭包里的旧值。


📌 总结

  • 原因:闭包导致定时器回调捕获的是旧的 count

  • 解决方案

    1. count 放到依赖数组 → 每次更新重建 effect。
    2. useRef 保存最新值 → 定时器只建一次但能读到最新数据。
    3. 用函数式 setState 避免依赖旧值。

http://www.xdnf.cn/news/1380439.html

相关文章:

  • Shi-Tomasi 算法和 Harris 角点检测算法都是经典的角点检测方法,但它们在理论基础和实现细节上有一些区别。下面我将详细对比这两种算法。
  • List<Map<String, String>>最简单的遍历方式
  • 【传奇开心果系列】Flet框架带图标带交互动画的办公用品费用占比统计饼图自定义模板
  • GitHub 热榜项目 - 日榜(2025-08-28)
  • 达梦数据库-重做日志文件(一)
  • 云计算学习100天-第30天
  • 09- AI大模型-docker部署dify以及 dify的使用案例:公司智能助手(构建知识库)(上篇)
  • TDengine 数据订阅支持 MQTT 协议用户手册
  • 【SQL】计算一年内每个月份的周数据
  • 上海控安:WiFi网络安全攻击
  • SONiC 之 Testbed(2)Ansible
  • GeoScene Maps 完整入门指南:从安装到实战
  • Android稳定性问题的常见原因是什么
  • 【python】@staticmethod装饰器
  • 同一个栅格数据,为何在QGIS和ArcGIS Pro中打开后显示的数值范围不同?
  • 苍穹外卖项目笔记day01
  • 【VSCode】使用VSCode打开md文件以及转化为PDF
  • uni-app 网络请求与后端交互完全指南:从基础到实战
  • ckman部署的clickhouse,节点迁移
  • Logstash数据迁移之es-to-kafka.conf详细配置
  • 用 Docker 玩转 Kafka 4.0镜像选型、快速起步、配置持久化与常见坑
  • 让模糊物体变清晰的视频AI:快速提升画质指南
  • 三维视频融合驱动视频孪生创新:智汇云舟引领数字孪生产业新范式
  • Runway Gen-2 深度技术解析:AI视频生成的范式变革
  • RAGFlow
  • 健永科技RFID技术在羊智能分群管理系统的使用案例
  • 传统星型拓扑结构的5G,WiFi无线通信网络与替代拓扑结构自组网
  • 【Linux】Linux基础开发工具从入门到实践
  • Foundry工具大全
  • 鸿蒙banner页实现