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

IT技术 javascript reactjs react-hooks
2021-03-18 04:58:36

最近我在研究 React Hooks 并且遇到了一个问题/疑问?

下面是重现问题的基本实现,在这里我只是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 中获得旧的 state 值那将非常有帮助

沙盒网址 - https://codesandbox.io/s/xp540ynomo

2个回答

这归结为闭包在 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>

@ UjinT34 是的,但是当_onClick第一次调用时,超时被创建,然后在一段时间后调用超时函数时,flag将在初始渲染的上下文中查找,因此它将获得“旧”值。
2021-04-26 04:58:36
什么时候setTimeout创建闭包我认为它每次_onClick被调用时都会被创建,并被flag捕获_onClick而不是原始flag变量。
2021-04-29 04:58:36
可能是我弄错了“初始渲染”。对我来说,这听起来像setTimeoutflag = false永远,因为它是第一个App电话。请检查我的答案。
2021-05-11 04:58:36
@ UjinT34 我认为如果您将其App视为一个函数会很有意义,并且每次重新渲染都会导致对该App函数的新调用第一次调用App不会“看到”第二次调用中的变量App
2021-05-15 04:58:36
没有在每次渲染时_onClick获得新的闭包和新flag值?
2021-05-21 04:58:36

给定函数将从函数中setTimeout获取flag变量_onClick_onClick函数在每次渲染时创建并“存储”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