React中无依赖useEffect内setInterval打印state值始终不变的原因咨询
我完全懂你的困惑!明明空依赖的useEffect只执行一次,setProgressBar也在正常更新状态,但console.log却一直输出初始值3000,这事儿的核心问题出在闭包上,咱们一步步拆解来看:
首先,先理清楚useEffect的执行逻辑:当组件第一次渲染时,这个空依赖的useEffect会触发执行,创建一个定时器。此时progressBar的初始值是3000,而定时器的回调函数会通过闭包“抓住”这个时刻的progressBar值——闭包的特性就是,函数会永久保留它创建时所在作用域里的变量状态。
接下来,每次调用setProgressBar更新state时,组件会重新渲染,生成一个新的progressBar值,但这个新值属于新的组件作用域。而咱们的定时器是第一次渲染时创建的,它的回调函数自始至终绑定的都是第一次渲染时的那个progressBar(也就是3000),所以不管state怎么更新,console.log输出的始终是旧的闭包值。
不过你可能会好奇,那为啥setProgressBar((prevVal) => prevVal -15)能正确更新进度条呢?这是因为你用了函数式更新的写法,这种写法会自动接收上一次的state值作为参数,它不依赖当前作用域里的progressBar变量,所以能绕过闭包的限制,正确拿到最新的前值来更新state,这也是为啥进度条其实在正常往前走,但log的内容不对的原因。
如果你想让console.log输出正确的state值,有两个常用的解决办法:
- 办法一:把
progressBar加到useEffect的依赖数组里。这样每次state更新,useEffect会重新执行,先销毁旧的定时器,再创建新的定时器,新的回调函数就会捕获最新的progressBar值,log就能正常输出了。 - 办法二:用
useRef来同步最新的state值。ref的current属性是可变的,不会被闭包捕获固定值,我们可以在每次state更新时同步ref的值,然后在定时器里log ref.current:const [progressBar, setProgressBar] = useState(3000); const progressRef = useRef(progressBar); // 同步state到ref useEffect(() => { progressRef.current = progressBar; }, [progressBar]); useEffect(() => { const progress = setInterval(() => { console.log(progressRef.current); setProgressBar((prevVal) => prevVal - 15); }, 15); return () => clearInterval(progress); }, []);
备注:内容来源于stack exchange,提问作者ABISHEK NS

