【问题标题】:React - useState - why setTimeout function does not have latest state value?React - useState - 为什么 setTimeout 函数没有最新的状态值?
【发布时间】:2019-08-07 11:20:45
【问题描述】:

最近我正在研究 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 中获得旧的状态值,那将非常有帮助

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

【问题讨论】:

    标签: javascript reactjs react-hooks


    【解决方案1】:

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

    【讨论】:

    • _onClick 不会在每次渲染时获得一个新的闭包和一个新的flag 值吗?
    • @UjinT34 是的,但是当第一次调用_onClick 时会创建超时,然后在稍后调用超时函数时,会在初始上下文中查找flag渲染,因此它将获得“旧”值。
    • 闭包setTimeout什么时候创建?我认为每次调用_onClick 时都会创建它,并获取_onClick 捕获的flag 而不是原始的flag 变量。
    • @UjinT34 如果您将App 视为一个函数,我认为这是有道理的,并且每次重新渲染都会导致对App 函数的新调用。第一次调用App 没有“看到”第二次调用App 中的变量。
    • 可能是我弄错了“初始渲染”。对我来说,这听起来像setTimeout 永远拥有flag = false,就像它在第一个App 通话中一样。请检查我的答案。
    【解决方案2】:

    赋予setTimeout 的函数将从_onClick 函数中获取flag 变量。每次渲染都会创建 _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
    

    【讨论】:

      猜你喜欢
      • 2023-03-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      • 2022-12-12
      相关资源
      最近更新 更多