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

相关推荐
小桥风满袖34 分钟前
极简三分钟ES6 - ES8中字符串扩展
前端·javascript
张拭心34 分钟前
这就是流量的力量吗?用豆包 AI 编程做的xhs小组件帖子爆了
前端·ai编程·豆包marscode
少年阿闯~~37 分钟前
CSS3的新特性
前端·javascript·css3
IT_陈寒44 分钟前
React性能优化:这5个Hook技巧让我的组件渲染效率提升50%(附代码对比)
前端·人工智能·后端
智能化咨询1 小时前
【Linux】【实战向】Linux 进程替换避坑指南:从理解 bash 阻塞等待,到亲手实现能执行 ls/cd 的 Shell
前端·chrome
Anson Jiang1 小时前
浏览器标签页管理:使用chrome.tabs API实现新建、切换、抓取内容——Chrome插件开发从入门到精通系列教程06
开发语言·前端·javascript·chrome·ecmascript·chrome devtools·chrome插件
掘金安东尼1 小时前
黑客劫持:周下载量超20+亿的NPM包被攻击
前端·javascript·面试
剑亦未配妥2 小时前
移动端触摸事件与鼠标事件的触发机制详解
前端·javascript
人工智能训练师8 小时前
Ubuntu22.04如何安装新版本的Node.js和npm
linux·运维·前端·人工智能·ubuntu·npm·node.js