在 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 每变一次,就会触发这样的流程:
- 先执行上一次的清理函数(打印上一个 count 值)
- 执行新的副作用逻辑(这里没写具体逻辑,主要是注册新清理函数)
- 注册和新 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); // 这样就能拿到最新值啦
};
}, []);
实用小技巧,记牢不踩坑
- 别偷懒,依赖写全:effect 里用到的状态、props,都乖乖放进依赖数组
- 别乱放空依赖:除非你百分百确定副作用不需要任何变量,否则别用空数组
- 清理函数用了啥,依赖里就得有啥:别让清理函数里的变量 "游离" 在依赖之外
- 开个 ESLint 插件:用eslint-plugin-react-hooks的exhaustive-deps规则,它会帮你检查漏写的依赖
总结一下
useEffect 的清理函数不是只在组件卸载时才工作 ------ 依赖数组里的变量变了,它也会先执行旧的清理函数。而且清理函数会 "记住" 创建时的变量值(闭包特性),所以想拿最新值,就得让它跟着变量一起更新~
记住这几点,以后用 useEffect 就不会被清理函数和依赖数组搞得头大啦!