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

React中如何在含超时的循环函数中获取最新useState值?

问题修复:解决React闭包导致的过时状态问题

你的问题出在闭包捕获了过时的state值:runLoop函数在组件首次渲染时被创建,捕获了当时的output初始值("Output: ")。当setTimeout延迟执行后,调用runLoop时,闭包里的output还是第一次渲染时的旧值,导致每次setOutput都基于旧值拼接,最终无法得到累加的正确结果。

方法一:使用setState的函数式更新

React的setState支持传入函数,函数会接收当前最新的state值作为参数,这样就能避开闭包带来的过时状态问题。

修改runLoop中的setOutput代码:

const runLoop = (maxIterations = 0) => {
  // 使用函数式更新,基于最新的prevOutput拼接
  setOutput(prevOutput => prevOutput + ", " + maxIterations);
  if (maxIterations >= 5) {
    setIsLooping(false);
    return;
  }
  sleep(1000)().then(() => {
    runLoop(maxIterations + 1);
  });
};

方法二:改用async/await + for循环(更直观)

把递归逻辑改成async函数配合for循环,避免闭包嵌套,逻辑更清晰:

首先简化sleep函数:

const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

然后重写runLoop:

const runLoop = async () => {
  let currentOutput = "Output: ";
  for (let i = 1; i <= 5; i++) {
    currentOutput += ", " + i;
    setOutput(currentOutput);
    await sleep(1000);
  }
  setIsLooping(false);
};

同时修改startLoop里的调用:

const startLoop = () => {
  if (isLooping) {
    console.log("Click ignored - because is Looping");
    return;
  } else {
    setIsLooping(true);
    // 重置初始值,避免重复点击时累加旧内容
    setOutput("Output: ");
    runLoop();
  }
};

完整修复后的代码(方法一示例)

import "./styles.css";
import React, { useEffect, useState } from "react";

export default function App() {
  const [output, setOutput] = useState("Output: ");
  const [isLooping, setIsLooping] = useState(false);

  const sleep = (ms) => {
    return function (x) {
      return new Promise((resolve) => setTimeout(() => resolve(x), ms));
    };
  };

  const startLoop = () => {
    if (isLooping) {
      console.log("Click ignored - because is Looping");
      return;
    } else {
      setIsLooping(true);
      // 重置output,避免重复启动时叠加旧内容
      setOutput("Output: ");
      runLoop();
    }
  };

  const runLoop = (maxIterations = 0) => {
    // 函数式更新获取最新state
    setOutput(prevOutput => prevOutput + ", " + maxIterations);
    if (maxIterations >= 5) {
      setIsLooping(false);
      return;
    }
    sleep(1000)().then(() => {
      runLoop(maxIterations + 1);
    });
  };

  useEffect(() => {
    startLoop();
  }, []);

  return (
    <div className="App">
      <p>{output}</p>
      <button
        value="Start Loop"
        onClick={() => {
          startLoop();
        }}
      >
        Start loop
      </button>
      {isLooping && <p>Is Looping</p>}
    </div>
  );
}

这样修改后,就能得到预期的输出:Output: , 0, 1, 2, 3, 4, 5(如果需要从1开始,调整maxIterations的初始值和判断条件即可)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 15:05:16