当前位置: 首页 > 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.dtcms.com/a/354475.html

相关文章:

  • 响应用户:React中的事件处理机制
  • [linux仓库]透视文件IO:从C库函数的‘表象’到系统调用的‘本质’
  • RSA+AES 混合加密不复杂,但落地挺烦,我用 Vue+PHP 封装成了两个库
  • XTUOJ C++小练习(素数的判断,数字塔,字母塔)
  • 亚马逊合规风控升级:详情页排查与多账号运营安全构建
  • Unity游戏打包——Android打包环境(Mac下)
  • PDF压缩如何平衡质量与体积?
  • Electron 简介:Node.js 桌面开发的起点
  • 小鹏自动驾驶的BEV占用网络有哪些优势?
  • “矿山”自动驾驶“路网”编辑功能实现
  • Mip-splatting
  • 在docker 中拉取xxl-job以及配置数据库
  • 【Linux】Linux基础开发工具从入门到实践
  • Redis 哨兵(Sentinel)全面解析
  • JavaSE丨集合框架入门:从0掌握Collection与List核心用法
  • Two Knights (数学)
  • Feign整合Sentinel实现服务降级与Feign拦截器实战指南
  • uni-app 网络请求与后端交互完全指南:从基础到实战
  • 智能养花谁更优?WebIDE PLOY技术与装置的结合及实践价值 —— 精准养护的赋能路径
  • 【LeetCode】29. 两数相除(Divide Two Integers)
  • PhotoshopImageGenerator:基于Photoshop的自动化图像数据集生成工具
  • C# 操作 DXF 文件指南
  • WAF对比传统防火墙的优劣势
  • 从Cgroups精准调控到LXC容器全流程操作​:用pidstat/stress测试Cgroups限流,手把手玩转Ubuntu LXC容器全流程​
  • 打破存储局限:CS 创世 SD NAND 如何优化瑞芯微(RK)与北京君正平台的贴片式 SD 卡性能
  • 横扫SQL面试——流量与转化率分类
  • 机器人电源电感的认证和认证细节,知多少?
  • Spring Boot 整合 SSE, http长连接
  • odoo打印新解
  • lesson48:Ubuntu下Python与三大数据库实战:MySQL、MongoDB、Redis全攻略