React中setInterval读取useState队列始终为空的问题排查
问题原因与解决方案
问题原因
这是React组件里典型的闭包陷阱问题:
- 你的
useEffect依赖项是空数组[],意味着它只会在组件首次挂载时执行一次。 setInterval的回调函数在挂载时就被创建,它捕获的是当时的nums状态——也就是初始的空数组[]。- 后续点击按钮更新
nums时,这个回调里的nums并不会同步更新,始终是挂载时的旧值,所以每次打印都是空数组,也没法正确处理队列元素。
解决方案
方案1:使用函数式更新setState
React的setState支持传入函数,函数参数是当前最新的状态值,能直接绕过闭包陷阱拿到最新的nums。修改useEffect内的代码:
import { useState, useEffect } from "react"; function App() { const [nums, setNums] = useState([]); useEffect(() => { const intervalId = setInterval(() => { setNums(prevNums => { console.log(`Nums > `, prevNums); const nextNums = [...prevNums]; nextNums.pop(); return nextNums; }); }, 5000); return () => clearInterval(intervalId); }, []); return ( <div> {JSON.stringify(nums)} <div> {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((val) => { return ( <div key={val} style={{ cursor: 'pointer' }} onClick={() => { setNums(prev => [...prev, val]) }}> {val} </div> ); })} </div> </div> ); } export default App;
补充:把点击事件里的
setNums也改成函数式更新,避免连续快速点击时丢失状态;同时给map元素加上key属性,符合React规范。
方案2:用useRef保存最新状态
如果需要在回调里直接读取最新状态,可以用useRef创建引用,每次nums更新时同步它的值:
import { useState, useEffect, useRef } from "react"; function App() { const [nums, setNums] = useState([]); const numsRef = useRef(nums); // 每次nums更新时同步ref的值 useEffect(() => { numsRef.current = nums; }, [nums]); useEffect(() => { const intervalId = setInterval(() => { console.log(`Nums > `, numsRef.current); const nextNums = [...numsRef.current]; nextNums.pop(); setNums(nextNums); }, 5000); return () => clearInterval(intervalId); }, []); return ( <div> {JSON.stringify(nums)} <div> {[1, 2, 3, 4, 5, 6, 7, 8, 9].map((val) => { return ( <div key={val} style={{ cursor: 'pointer' }} onClick={() => { setNums([...nums, val]) }}> {val} </div> ); })} </div> </div> ); } export default App;
内容的提问来源于stack exchange,提问作者noi.m
相关产品推荐
相关产品推荐

