提问人:Korimako 提问时间:5/11/2023 最后编辑:Korimako 更新时间:5/11/2023 访问量:52
如何在从循环函数调用的函数中使用新的 useState 值,包括 React 中的超时?
How can I use a fresh useState value in a function called from a looped function including a timeout in React?
问:
在此代码中,我希望输出打印“Output:,1,2,3,4,5” 但是输出变量是过时的,因为我在从闭包返回 promise 后迭代调用该函数。
有什么有效的方法可以保持这种定时睡眠效果的循环,并获得我想要的结果?
代码沙盒:https://codesandbox.io/s/hidden-shape-6uh5jm?file=/src/App.js:0-1092
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);
runLoop();
}
};
const runLoop = (maxIterations = 0) => {
setOutput(output + ", " + 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>
);
}
答:
0赞
Drew Reese
5/11/2023
#1
每当下一个状态值依赖于前一个状态值时,例如,将一个值连接到前一个值,您将需要使用功能状态更新。
更新处理程序以使用功能状态更新,这实际上是一个相当微不足道的更改:到 .runLoop
setOutput(output + ", " + maxIterations);
setOutput(output => output + ", " + maxIterations);
const runLoop = (maxIterations = 0) => {
setOutput(output => output + ", " + maxIterations);
if (maxIterations >= 5) {
setIsLooping(false);
return;
}
sleep(1000)().then(() => {
runLoop(maxIterations + 1);
});
};
不过,这会导致一个奇怪的输出:"Output: , 0, 1, 2, 3, 4, 5"
这基本上是一个典型的篱笆问题。
这是将要呈现的 UI 与要从中呈现的数据混合在一起的结果,全部进入状态。请记住,在 React 中,UI(例如渲染的内容)是状态和 props 的函数。React 状态应该只包含你需要存储的数据,而 React 组件将状态映射到渲染的 UI。
下面是一个仅存储值并呈现该状态的计算输出字符串的示例。maxIterations
import "./styles.css";
import React, { useEffect, useState } from "react";
export default function App() {
const [output, setOutput] = useState([]); // <-- initial empty array
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 {
// reset array for next looping
setOutput([]);
setIsLooping(true);
runLoop();
}
};
const runLoop = (maxIterations = 0) => {
setOutput((output) => output.concat(maxIterations)); // <-- append new value, return new array
if (maxIterations >= 5) {
setIsLooping(false);
return;
}
sleep(1000)().then(() => {
runLoop(maxIterations + 1);
});
};
useEffect(() => {
startLoop();
}, []);
return (
<div className="App">
<p>Output: {output.join(", ")}</p> // <-- compute rendered UI
<button
value="Start Loop"
onClick={() => {
startLoop();
}}
>
Start loop
</button>
{isLooping && <p>Is Looping</p>}
</div>
);
}
渲染的输出现在将更令人期待"Output: 0, 1, 2, 3, 4, 5"
演示
下一个:回调函数未更新状态
评论
setOutput(output + ", " + maxIterations);
自?setOutput(currentValue => currentValue + ", " + maxIterations);