状态更新被另一个(先前的)状态更新覆盖?

时间:2021-06-22 08:27:46

标签: javascript reactjs react-hooks

我知道这似乎是一个不寻常的例子,但我似乎仍然无法准确解释为什么我在单击 div 后从未看到 valueB 打印在控制台上?

请注意,由于我在 setTimeout 中调用了两个 set state 调用,它们没有被批处理

function App() {
  let [a, setA] = React.useState();
  let [b, setB] = React.useState();

  React.useEffect(() => {
    console.log('Entering useEffect', a, b);

    return () => {
      console.log('Entering cleanup', a, b);

      setA(null);
      setB(null);
    };
  }, [a, b]);

  console.log('Render', a, b);

  return (
    <div
      onClick={() => {
        setTimeout(() => {
          setA('valueA');
          setB('valueB');
        }, 100);
      }}
    >
      <h1>Test App</h1>
    </div>
  );
}

ReactDOM.render(
  <App/>,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

1 个答案:

答案 0 :(得分:1)

通常 useEffect 及其清理在渲染后异步调用(在另一个堆栈上)。但是,如果您在计时器中调用 setState 并且有 useEffect 个回调,它们会被急切地调用,但是所有从清理中累积的状态变化都被调用 after setState 调用这个操作。

因此在您的示例中,当调用 setTimeout 处理程序时:

  • 调用 setA 时:a 状态已更改,并且将清除中的两个状态更改添加到待处理状态更改队列
  • 调用 setB 时:首先将 valueB 应用于 b,然后从清理中应用 null(这里是一种批处理)< /li>

这试图模仿状态更新实际上是在点击处理程序中进行批处理时的行为(首先从点击处理程序应用状态更新,然后从 useEffect 应用)。

当您使用更新程序功能时,您可以更清楚地看到发生了什么:

function App() {
  Promise.resolve().then(() => console.log("**** another stack ****"));
  console.log("before useStateA");
  let [a, setA] = React.useState();
    console.log("between useStates");
  let [b, setB] = React.useState();
    console.log("after useStateB");
  

  React.useEffect(() => {
    console.log('Entering useEffect', a, b);

    return () => {
      console.log('Entering cleanup', a, b);

      setA(() => (console.log("setting a to null from cleanup"), null));
      setB(() => (console.log("setting b to null from cleanup"), null));
    };
  }, [a, b]);

  console.log('Render', a, b);

  return (
    <div
      onClick={() => {
        setTimeout(() => {
          console.log("****timer start****");
          setA(() => (console.log("setting a to valueA from timer"), "valueA"));
          console.log("between timer setters");
          setB(() => (console.log("setting b to valueB from timer"), "valueB"));
          console.log("****timer end****");
        }, 100);
      }}
    >
      <h1>Test App</h1>
    </div>
  );
}

ReactDOM.render(
  <App/>,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>