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
相关产品推荐
相关产品推荐

