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 就不会被清理函数和依赖数组搞得头大啦!

相关推荐
阳光阴郁大boy1 小时前
一个基于纯前端技术实现的五子棋游戏,无需后端服务,直接在浏览器中运行。
前端·游戏
石小石Orz1 小时前
效率提升一倍!谈谈我的高效开发工具链
前端·后端·trae
EndingCoder1 小时前
测试 Next.js 应用:工具与策略
开发语言·前端·javascript·log4j·测试·全栈·next.js
xw51 小时前
免费的个人网站托管-PinMe篇
服务器·前端
!win !1 小时前
免费的个人网站托管-PinMe篇
前端·前端工具
牧天白衣.1 小时前
CSS中linear-gradient 的用法
前端·css
军军3601 小时前
Git大型仓库的局部开发:分步克隆 + 指定目录拉取
前端·git
前端李二牛1 小时前
Vue3 特性标志
前端·javascript
coding随想2 小时前
JavaScript事件处理程序全揭秘:从HTML到IE的各种事件绑定方法!
前端
搞个锤子哟2 小时前
关键词匹配,过滤树
前端