在react中hook的闭包问题很容易在不经意间犯错,项目写大了之后更是难以找到到底是哪里出了问题。
为什么会出现闭包问题
出现闭包问题的原因就是函数中操作的变量不是最新的变量
,什么意思呢,我们知道函数组件每次刷新都是重新运行一次函数,这就会导致,每次刷新都会产生新的变量,但是如果用useEffect把依赖性置为空数组的话,那么useEffect中的回调就不会再次执行,导致回调中保存的仍是刷新前的变量。
代码
javascript
import { useEffect } from "react";
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 500);
return () => {
clearInterval(interval)
};
}, []);
return (
<div>
{count}
</div>
);
}
export default App;
运行效果如下

我们可以看到代码并没有我们预期的结果,加到1就停止增加了,为什么呢?
我们分析一下APP函数的运行次数。
第一次运行count = 0
过一秒钟后因为会setCount导致count = 1
然后刷新页面,重新运行APP函数,在内存中生成新的count变量,useEffect因为依赖项为[]
所以不在再次执行,setInterval中的函数保存的还是第一次函数执行的count变量,setInterval后续的每次执行操作的都是第一次的变量导致一直setCount(0 + 1),App函数并不会再次刷新了。
看图

现在知道了闭包的产生原因,就是因为函数中操作的变量不是最新的,那解决办法就很明显了。
- 函数前后刷新都会产生新的变量,那我们让他不产生新的不就可以了,这就可以使用
useRef
这个钩子了。
javascript
import { useEffect, useRef } from "react";
import { useState } from "react";
function App() {
const countRef = useRef(0);
const [_, forceUpdate] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
console.log('count', countRef.current)
countRef.current += 1;
forceUpdate(Math.random())
}, 500);
return () => {
clearInterval(interval)
};
}, []);
return (
<div>
{countRef.current}
</div>
);
}
export default App;
操作方式

- 每次组件刷新的时候都让函数重新执行,让函数去操作最新变量就可以了
javascript
import { useEffect } from "react";
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 500);
return () => {
clearInterval(interval)
};
}, [count]);
return (
<div>
{count}
</div>
);
}
export default App;
操作方式
- 闭包导致了这个问题,那我们就不让他产生闭包,这就用到了setCount的另一个重载的形式。
javascript
import { useEffect } from "react";
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
// 使用箭头函数,可以获取到最新的count值
setCount(count => count + 1);
}, 500);
return () => {
clearInterval(interval)
};
}, []);
return (
<div>
{count}
</div>
);
}
export default App;
我们把函数传入setCount
中,在setCount
执行的时候会自动把最新的count
值传入,就不会出现闭包导致每次操作的count都是0的情况了。
创造不易,喜欢留下个👍 吧