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

gio*_*gim 6 javascript reactjs react-hooks

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

请注意,由于我在 a 中调用了两个 set state 调用setTimeout因此它们不是批处理的

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")
);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

mar*_*lin 9

通常useEffect,它的清理在渲染后异步调用(在另一个堆栈上)。但是,如果您调用setState计时器并且存在回调,则会急切地调用它们,但在调用此操作之后useEffect会调用清理中累积的所有状态更改。 setState

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

  • 调用时setAa状态发生更改,并且清理中的两个状态更改被添加到挂起的状态更改队列中
  • 调用时setB:首先valueB应用于清理b,然后null从清理中应用(这里是批处理的)

这试图模仿状态更新实际批处理时的行为,就像在单击处理程序中一样(首先从单击处理程序应用状态更新,然后从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")
);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)