React useEffect 清理函数:别让依赖数组坑了你!

在 React 里写组件时,useEffect这玩意儿肯定绕不开 ------ 它是处理各种副作用的 "大管家"。但很多小伙伴都会被它的清理函数和依赖数组搞晕,今天咱们就用大白话聊聊这俩到底咋回事儿,帮你避开那些常见的坑~

先搞懂:useEffect 和清理函数是啥

useEffect说白了就是用来处理 "副作用" 的,比如发请求、绑事件监听、开定时器这些操作。它长这样:

scss 复制代码
useEffect(() => {
  // 这里写副作用逻辑(比如启动定时器)
  return () => {
    // 这里是清理函数(比如清除定时器)
  };
}, [dependencies]); // 这个数组就是依赖数组

那清理函数有啥用呢?简单说就是 "擦屁股" 用的:

  • 防止内存泄漏(比如事件监听忘了解绑)
  • 避免组件都卸了还瞎执行操作
  • 把上一次副作用留下的痕迹清干净

清理函数啥时候会执行?不止卸载时哦!

很多人以为清理函数只有在组件 "死翘翘"(卸载)时才执行,其实不是!它有两种出场机会:

1. 组件卸载时

这是最直观的场景 ------ 组件从页面上消失时,清理函数会跑一趟,把没做完的收尾工作搞定。

2. 依赖数组里的值变了

这才是容易踩坑的地方!当依赖数组里的变量变了,React 会先让上一次的清理函数执行,然后再跑新的副作用逻辑,最后注册新的清理函数。

你可以理解成:每次依赖变了,都要 "先收拾旧摊子,再摆新摊子",这样才不会乱糟糟~

常见坑解析:为啥我的清理函数不听话?

坑 1:依赖数组里有变量,清理函数老执行

代码例子
scss 复制代码
const [count, setCount] = useState(0);
useEffect(() => {
  return () => {
    console.log("Cleanup 执行", count);
  };
}, [count]); // 依赖数组里有count
现象

每次点按钮改 count,控制台就会打印 "Cleanup 执行",不是只有卸载时才打印!

为啥会这样?

因为依赖数组里有 count,所以 count 每变一次,就会触发这样的流程:

  1. 先执行上一次的清理函数(打印上一个 count 值)
  2. 执行新的副作用逻辑(这里没写具体逻辑,主要是注册新清理函数)
  3. 注册和新 count 绑定的清理函数
举个栗子
  • 刚开始 count=0:注册了个记着 count=0 的清理函数
  • 点一下 count=1:先执行上一个清理函数(打印 0),再注册记着 count=1 的新清理函数
  • 再点一下 count=2:执行记着 1 的清理函数(打印 1),注册记着 2 的新清理函数
  • 最后组件卸载:执行记着 2 的清理函数(打印 2)

坑 2:依赖数组为空,清理函数拿不到最新值

代码例子
scss 复制代码
const [count, setCount] = useState(0);
useEffect(() => {
  return () => {
    console.log("Cleanup 执行", count);
  };
}, []); // 依赖数组是空的
现象

不管怎么点按钮改 count,最后组件卸载时,清理函数永远打印 0!

为啥会这样?

依赖数组为空时,useEffect只在组件第一次渲染时跑一次,清理函数也是这时候创建的。它就像拍了张快照,把当时的 count=0 记在了心里(这就是闭包的特性)。

之后不管 count 怎么变,因为依赖数组是空的,useEffect不会重新执行,清理函数也不会更新。所以最后执行的还是最初那个 "快照版" 清理函数,自然就只认识 0 啦~

怎么解决这些坑?

想要清理函数拿到最新值?加依赖!

最简单的办法就是把用到的变量放进依赖数组:

javascript 复制代码
useEffect(() => {
  return () => {
    console.log("Cleanup 执行", count); // 这样就能拿到最新count啦
  };
}, [count]); // 记得把count加进来

这样每次 count 变了,都会生成新的清理函数,自然就能拿到最新值~

想减少清理函数执行次数?精简依赖!

如果清理函数执行太频繁,可以试试合并状态或者用useRef存中间值,但千万别为了省事漏掉必要的依赖哦!

特殊情况:依赖数组必须为空,但要最新值

这种时候可以用useRef当 "中转站":

scss 复制代码
const countRef = useRef(count);
// 实时更新ref里的值
useEffect(() => {
  countRef.current = count;
}, [count]);
// 依赖数组为空的effect
useEffect(() => {
  return () => {
    console.log("Cleanup 执行", countRef.current); // 这样就能拿到最新值啦
  };
}, []);

实用小技巧,记牢不踩坑

  1. 别偷懒,依赖写全:effect 里用到的状态、props,都乖乖放进依赖数组
  2. 别乱放空依赖:除非你百分百确定副作用不需要任何变量,否则别用空数组
  3. 清理函数用了啥,依赖里就得有啥:别让清理函数里的变量 "游离" 在依赖之外
  4. 开个 ESLint 插件:用eslint-plugin-react-hooks的exhaustive-deps规则,它会帮你检查漏写的依赖

总结一下

useEffect 的清理函数不是只在组件卸载时才工作 ------ 依赖数组里的变量变了,它也会先执行旧的清理函数。而且清理函数会 "记住" 创建时的变量值(闭包特性),所以想拿最新值,就得让它跟着变量一起更新~

记住这几点,以后用 useEffect 就不会被清理函数和依赖数组搞得头大啦!

相关推荐
仰望星空的小猴子11 分钟前
React18和React19新特性
前端
小码哥_常13 分钟前
Android新航标:Navigation 3为何成为变革先锋?
前端
SuperEugene13 分钟前
Vue状态管理扫盲篇:状态管理中的常见坑 | 循环依赖、状态污染与调试技巧
前端·vue.js·面试
骑着小黑马14 分钟前
从 Electron 到 Tauri 2:我用 3.5MB 做了个音乐播放器
前端·vue.js·typescript
aykon15 分钟前
DataSource详解以及优势
前端
Mintopia15 分钟前
戴了 30 天智能手环后,我才发现自己一直低估了“睡眠”
前端
leolee1815 分钟前
react redux 简单使用
前端·react.js·redux
仰望星空的小猴子16 分钟前
常用的Hooks
前端
天才熊猫君17 分钟前
Vue Fragment 锚点机制
前端
米丘17 分钟前
Git 常用操作命令
前端