You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

useEffect包裹setInterval/timeout时为何无法获取最新状态值

React中useEffect内定时器无法获取最新状态问题

问题表现

  • useEffect逻辑内部包裹setInterval或setTimeout定时器时,定时器回调中无法获取到state的最新更新值,所有读取到的状态始终停留在初始值
  • 点击启停按钮无法正常控制计时逻辑
  • 控制台打印的状态值不会随更新变化

问题复现代码

import { useState, useEffect } from "react";
let timerRef;
const StopWatch = () => {
  const [time, setTime] = useState(0);
  const [isPause, setPause] = useState(false);
  useEffect(() => {
    timerRef = setInterval(() => {
      console.log({ time });
      if (!isPause) {
        setTime(time + 1);
      }
    }, 5000);
    return () => clearInterval(timerRef);
  }, []);
  const startWatch = () => {
    setPause(false);
  };
  const stopWatch = () => {
    setPause(true);
  };
  return (
    <>
      time: {time}
      <button onClick={startWatch}>Start</button>
      <button onClick={stopWatch}>stop</button>
    </>
  );
};
export { StopWatch };

问题运行效果截图

问题原因

本质是JavaScript闭包陷阱:示例中给useEffect传入了空依赖数组[],意味着effect逻辑只会在组件首次挂载时执行一次,此时创建的定时器回调,捕获的是组件首次渲染时的状态上下文,time和isPause两个值永远是初始状态。后续state更新触发组件重渲染时,不会重新执行effect创建新的定时器回调,旧回调持有的闭包引用始终指向首次渲染的状态值,自然读不到更新。

解决方案

方案1:useRef同步可变状态 + 函数式状态更新

通过useRef存储需要在定时器中读取的可变状态,每次状态更新时同步到ref的current属性上;更新state时使用函数式写法,不依赖闭包中缓存的state值,永远基于最新状态计算。这种方案不需要重建定时器,计时精度更高。

import { useState, useEffect, useRef } from "react";
const StopWatch = () => {
  const [time, setTime] = useState(0);
  const [isPause, setPause] = useState(false);
  // 用ref存储定时器实例、最新的暂停状态
  const timerRef = useRef(null);
  const isPauseRef = useRef(isPause);

  // 暂停状态更新时同步到ref
  useEffect(() => {
    isPauseRef.current = isPause;
  }, [isPause]);

  useEffect(() => {
    timerRef.current = setInterval(() => {
      if (!isPauseRef.current) {
        // 函数式更新不依赖闭包缓存的time,永远取最新值计算
        setTime(prevTime => prevTime + 1);
      }
    }, 1000);
    return () => clearInterval(timerRef.current);
  }, []);

  const startWatch = () => setPause(false);
  const stopWatch = () => setPause(true);

  return (
    <>
      time: {time}
      <button onClick={startWatch}>Start</button>
      <button onClick={stopWatch}>stop</button>
    </>
  );
};
export { StopWatch };

方案2:正确声明useEffect依赖项

把定时器回调中用到的所有state都加入useEffect的依赖数组,每次依赖变化时,会先执行上一轮effect的清理函数清除旧定时器,再重新执行effect创建新的定时器,新回调就能捕获到最新的state值。

import { useState, useEffect, useRef } from "react";
const StopWatch = () => {
  const [time, setTime] = useState(0);
  const [isPause, setPause] = useState(false);
  const timerRef = useRef(null);

  useEffect(() => {
    timerRef.current = setInterval(() => {
      console.log({ time });
      if (!isPause) {
        setTime(time + 1);
      }
    }, 1000);
    return () => clearInterval(timerRef.current);
  // 将回调中用到的state加入依赖数组
  }, [time, isPause]);

  const startWatch = () => setPause(false);
  const stopWatch = () => setPause(true);

  return (
    <>
      time: {time}
      <button onClick={startWatch}>Start</button>
      <button onClick={stopWatch}>stop</button>
    </>
  );
};
export { StopWatch };

注意:该方案每次time更新都会销毁重建定时器,实际计时间隔容易出现偏差,对时间精度要求高的场景优先选择方案1。

内容的提问来源于stack exchange,提问作者Akhilesh Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 03:18:16