React - useState - 为什么 setTimeout 函数没有最新的状态值?

React - useState - why setTimeout function does not have latest state value?

最近我在研究 React Hooks,但遇到了一个 problem/doubt?

下面是重现问题的基本实现,这里我只是在单击按钮时切换 flag(状态)变量。

  const [flag, toggleFlag] = useState(false);
  const data = useRef(null);
  data.current = flag;

  const _onClick = () => {
    toggleFlag(!flag);
    // toggleFlag(!data.current); // working

    setTimeout(() => {
      toggleFlag(!flag); // does not have latest value, why ?
      // toggleFlag(!data.current); // working
    }, 2000);
  };

  return (
    <div className="App">
      <button onClick={_onClick}>{flag ? "true" : "false"}</button>
    </div>
  );

我想出了一些其他方法来解决这个问题,比如使用 useRef 或 useReducer,但这是正确的还是有任何其他方法可以仅使用 useState 来解决这个问题?

此外,如果有人能解释为什么我们在 setTimeout 中获取旧状态值,那将非常有帮助。

沙盒 URL - https://codesandbox.io/s/xp540ynomo

这归结为闭包在 JavaScript 中的工作方式。赋予 setTimeout 的函数将从初始渲染中获取 flag 变量,因为 flag 未发生变化。

您可以改为将函数作为参数提供给 toggleFlag。此函数将获取正确的 flag 值作为参数,此函数返回的内容将替换状态。

例子

const { useState } = React;

function App() {
  const [flag, toggleFlag] = useState(false);

  const _onClick = () => {
    toggleFlag(!flag);

    setTimeout(() => {
      toggleFlag(flag => !flag)
    }, 2000);
  };

  return (
    <div className="App">
      <button onClick={_onClick}>{flag ? "true" : "false"}</button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

setTimeout的函数将从_onClick函数中获取flag变量。 _onClick 函数在每次渲染时创建,"stores" flag 变量在此渲染中获得的值。

function App() {
  const [flag, toggleFlag] = useState(false);
  console.log("App thinks that flag is", flag);

  const _onClick = () => {
    console.log("_onClick thinks that flag is", flag);
    toggleFlag(!flag);

    setTimeout(() => {
      console.log("setTimeout thinks that flag is", flag);
    }, 100);
  };

  return (
    <div className="App">
      <button onClick={_onClick}>{flag ? "true" : "false"}</button>
    </div>
  );
}

控制台:

App thinks that flag is false

_onClick thinks that flag is false
App thinks that flag is true
setTimeout thinks that flag is false

_onClick thinks that flag is true
App thinks that flag is false
setTimeout thinks that flag is true