为何useState在DOM中更新正常,但控制台始终输出初始值?
useState定时器中变量值不更新的问题
我用useState实现了一个带计时器的函数组件,DOM能实时正确显示时间,但停止计时器时把时间存入数组,却始终存初始值0。调试发现定时器循环里console.log(time)一直输出0,不是实际时间。
原始代码:
export default function Timer() { const [time, setTime] = useState(0); const interval = useRef(null); clearInterval(interval.current); let startTime = Date.now() - time; interval.current = setInterval(() => { setTime(Date.now() - startTime); console.log(time); // <-- 为什么一直显示0? }, 10); return ( <div> <span>{"0" + Math.floor((time / 60000) % 1000)).slice(-2)}:</span> <span>{"0" + Math.floor((time / 1000) % 1000)).slice(-2)}.</span> <span>{"00" + (time % 1000)).slice(-3, -1)}</span> </div> ); }
我的疑问:为什么time在DOM里能实时显示正确值,控制台却一直输出0?原以为是useState更新不及时,但始终输出0的情况无法理解。
编辑:
我通过声明单独变量curTime解决了问题,修改后的代码如下:
export default function Timer() { const [time, setTime] = useState(0); const interval = useRef(null); let curTime = 0 clearInterval(interval.current); let startTime = Date.now() - time; interval.current = setInterval(() => { curTime = (Date.now() - startTime); setTime(curTime) console.log(curTime); // <-- 现在显示正确时间了 }, 10); return ( <div> <span>{"0" + Math.floor((time / 60000) % 1000)).slice(-2)}:</span> <span>{"0" + Math.floor((time / 1000) % 1000)).slice(-2)}.</span> <span>{"00" + (time % 1000)).slice(-3, -1)}</span> </div> ); }
问题原因与解释
这是React函数组件里闭包特性导致的典型坑:
- 函数组件每次渲染都会重新执行整个组件代码,生成全新的变量环境。
useState返回的time是当前渲染周期的“快照”——每次渲染的time都是独立的变量。 - 你在组件第一次渲染时创建了定时器,定时器的回调函数会“捕获”当时的
time变量(也就是初始值0)。之后哪怕setTime触发组件重新渲染,旧的定时器回调还是持有第一次渲染时的time引用,不会自动更新到新的time值,所以console.log(time)永远输出0。 - 而DOM能正确显示,是因为每次
setTime都会触发组件重新渲染,新的渲染周期会拿到最新的time快照,用来渲染UI自然是正确的。
你用curTime解决问题的思路是对的:这个变量是定时器回调里每次实时计算出来的,不受闭包捕获旧变量的影响,直接把最新值传给setTime,打印它当然能得到正确结果。
再给两个更符合React习惯的优化方案:
- 用
useRef存储实时值useRef的.current属性不会随渲染周期变化,能一直保存最新值:
export default function Timer() { const [time, setTime] = useState(0); const timeRef = useRef(0); const interval = useRef(null); clearInterval(interval.current); let startTime = Date.now() - time; interval.current = setInterval(() => { const newTime = Date.now() - startTime; timeRef.current = newTime; setTime(newTime); console.log(timeRef.current); // 这里能拿到实时时间 }, 10); return ( <div> <span>{"0" + Math.floor((time / 60000) % 1000)).slice(-2)}:</span> <span>{"0" + Math.floor((time / 1000) % 1000)).slice(-2)}.</span> <span>{"00" + (time % 1000)).slice(-3, -1)}</span> </div> ); }
- 用
setTime的函数式更新
如果需要基于旧state计算新值,直接给setTime传函数,就能拿到最新的旧值:
setTime(prevTime => { const newTime = Date.now() - startTime; console.log(newTime); // 这里能拿到实时计算的时间 return newTime; });
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

