React useState状态更新后console.log无法获取最新值问题求助
这是典型的React闭包陷阱,你的decrementTime函数被useEffect里的setInterval捕获后,一直保留着组件第一次渲染时的remainingTime值(也就是初始的10)。
问题根源
useEffect的依赖数组仅包含remainingSeconds,因此只有当remainingSeconds变化时,才会重新执行useEffect并创建新定时器。- 第一次渲染时,
decrementTime函数里的remainingTime被固定为初始值10,这个函数被定时器回调引用后,就被闭包“锁死”了。后续组件重新渲染时生成的新decrementTime函数,根本不会被旧定时器调用——因为定时器还是第一次创建的那个,它只认最初的decrementTime。
所以哪怕页面渲染的remainingTime已经正常递减,decrementTime里读取的始终是闭包捕获的旧值,导致console.log一直显示10,if(remainingTime>0)也永远为true。
解决方案
方案1:将判断逻辑移到函数式更新回调中
既然已经在使用函数式更新setRemainingTime((currentTime) => currentTime - 1),可以直接把判断逻辑嵌入这个回调,完全依赖最新的当前状态:
function decrementTime(): void { setRemainingTime((currentTime) => { const newTime = currentTime > 0 ? currentTime - 1 : currentTime; console.log("function gets called, state is : " + newTime); return newTime; }); }
这样每次调用setRemainingTime时,都会拿到最新的状态值,判断和更新逻辑都基于当前最新值,console.log也能输出正确数值。
方案2:用useCallback包裹函数并更新依赖
把decrementTime用useCallback包裹,让它依赖remainingTime,同时将decrementTime加入useEffect的依赖数组,确保状态变化时定时器能调用最新的函数:
import React, { useEffect, useState, useCallback } from "react"; import { FC } from "react"; interface ITimerProps { remainingSeconds: number; } const Timer: FC<ITimerProps> = ({ remainingSeconds = 10 }) => { const [remainingTime, setRemainingTime] = useState(remainingSeconds); const decrementTime = useCallback(() => { if (remainingTime > 0) { setRemainingTime((currentTime) => currentTime - 1); } console.log("function gets called, state is : " + remainingTime); }, [remainingTime]); useEffect(() => { const timer = setInterval(() => { decrementTime(); }, 1000); return () => { clearInterval(timer); }; }, [remainingSeconds, decrementTime]); return <div>{remainingTime}</div>; }; export default Timer;
注意:这种方式每次状态变化都会销毁旧定时器、创建新定时器,虽然能解决问题,但对于倒计时场景来说,微小的性能影响完全可以忽略。
方案3:用useRef保存最新状态
使用useRef存储最新的remainingTime,因为ref的current属性是可变的,不会被闭包捕获旧值。每次状态更新时同步更新ref,在decrementTime里直接读取ref的current:
import React, { useEffect, useState, useRef } from "react"; import { FC } from "react"; interface ITimerProps { remainingSeconds: number; } const Timer: FC<ITimerProps> = ({ remainingSeconds = 10 }) => { const [remainingTime, setRemainingTime] = useState(remainingSeconds); const remainingTimeRef = useRef(remainingSeconds); // 同步状态到ref useEffect(() => { remainingTimeRef.current = remainingTime; }, [remainingTime]); useEffect(() => { const timer = setInterval(() => { decrementTime(); }, 1000); return () => { clearInterval(timer); }; }, [remainingSeconds]); function decrementTime(): void { if (remainingTimeRef.current > 0) { setRemainingTime((currentTime) => currentTime - 1); } console.log("function gets called, state is : " + remainingTimeRef.current); }; return <div>{remainingTime}</div>; }; export default Timer;
这种方式不需要频繁创建销毁定时器,适合对性能有更精细要求的场景。
内容的提问来源于stack exchange,提问作者Enyak Stew

