在 setTimeout 内多次更新状态
Updating state multiple times within a setTimeout
我在 React 中有一些代码基本上设置了一系列超时的状态。这个想法是一个接一个地逐渐改变数组中的值。
const clickHandler = () => {
for (let i = 0; i < values.length; i++) {
setTimeout(() => {
const newValues = [...values];
newValues[i] = "-";
setValues(newValues);
}, (i + 1) * 500);
}
};
但是,我注意到只有最新的值得到更新(而以前的值会返回到它们的原始状态)。似乎 setValues 在调用后续超时时不会更新。也许 setValues 正在一起批处理。
我可以重构代码以使用另一个变量并在 useEffect 中进行更改,但只是想确认我对为什么会出现此问题的理解。
这是一个最小的、可验证的例子。您需要使用 functional update 因为 values
每次都引用原始值。我推荐 async
和 await
,因为与过时的 setTimeout
.
相比,它们提供更好的控制流
async function onClick(event) {
for (const [index, _] of values.entries()) {
setValues(v => [
...v.slice(0, index),
"",
...v.slice(index + 1)
])
await new Promise(r => setTimeout(r, 500))
}
}
Whosebug 不支持 React 片段中的 async
和 await
,所以我不得不使用 reduce
和 promise.then
。 运行 看看功能更新如何解决您的问题。
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
const update = (arr, index, f) =>
[...arr.slice(0, index), f(arr[index]), ...arr.slice(index + 1)]
function App() {
const [values, setValues] = React.useState(["", "", "", ""])
function onClick(event) {
values.reduce((prom, value, index) =>
prom
.then(_ => sleep(500))
.then(_ => setValues(v => update(v, index, _ => ""))),
Promise.resolve()
)
}
return <p>
{values.join(" ")}<br/>
Let's eat! <button onClick={onClick} children="いただきます" />
</p>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
p { margin: 0; font-size: 4rem; line-height: 4rem; font-family: sans-serif; }
button { font-size: 2rem; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
我在 React 中有一些代码基本上设置了一系列超时的状态。这个想法是一个接一个地逐渐改变数组中的值。
const clickHandler = () => {
for (let i = 0; i < values.length; i++) {
setTimeout(() => {
const newValues = [...values];
newValues[i] = "-";
setValues(newValues);
}, (i + 1) * 500);
}
};
但是,我注意到只有最新的值得到更新(而以前的值会返回到它们的原始状态)。似乎 setValues 在调用后续超时时不会更新。也许 setValues 正在一起批处理。
我可以重构代码以使用另一个变量并在 useEffect 中进行更改,但只是想确认我对为什么会出现此问题的理解。
这是一个最小的、可验证的例子。您需要使用 functional update 因为 values
每次都引用原始值。我推荐 async
和 await
,因为与过时的 setTimeout
.
async function onClick(event) {
for (const [index, _] of values.entries()) {
setValues(v => [
...v.slice(0, index),
"",
...v.slice(index + 1)
])
await new Promise(r => setTimeout(r, 500))
}
}
Whosebug 不支持 React 片段中的 async
和 await
,所以我不得不使用 reduce
和 promise.then
。 运行 看看功能更新如何解决您的问题。
const sleep = ms =>
new Promise(r => setTimeout(r, ms))
const update = (arr, index, f) =>
[...arr.slice(0, index), f(arr[index]), ...arr.slice(index + 1)]
function App() {
const [values, setValues] = React.useState(["", "", "", ""])
function onClick(event) {
values.reduce((prom, value, index) =>
prom
.then(_ => sleep(500))
.then(_ => setValues(v => update(v, index, _ => ""))),
Promise.resolve()
)
}
return <p>
{values.join(" ")}<br/>
Let's eat! <button onClick={onClick} children="いただきます" />
</p>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
p { margin: 0; font-size: 4rem; line-height: 4rem; font-family: sans-serif; }
button { font-size: 2rem; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>