在 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 每次都引用原始值。我推荐 asyncawait,因为与过时的 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 片段中的 asyncawait,所以我不得不使用 reducepromise.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>