React hooks:为什么异步函数中的多个 useState setter 会导致多次重新渲染?

And*_*dru 31 javascript reactjs react-hooks

以下 onClick 回调函数将导致 1 次重新渲染:

const handleClickSync = () => {
  // Order of setters doesn't matter - React lumps all state changes together
  // The result is one single re-rendering
  setValue("two");
  setIsCondition(true);
  setNumber(2);
};
Run Code Online (Sandbox Code Playgroud)

React 将所有三个状态更改集中在一起并导致 1 次重新渲染。

然而,以下 onClick 回调函数将导致 3 次重新渲染:

const handleClickAsync = () => {
  setTimeout(() => {
    // Inside of an async function (here: setTimeout) the order of setter functions matters.
    setValue("two");
    setIsCondition(true);
    setNumber(2);
  });
};
Run Code Online (Sandbox Code Playgroud)

这对每个设置者来说都是一次重新渲染useState。此外,设置器的顺序会影响每个渲染中的值。

问题:为什么我使函数异步(此处为 via setTimeout)这一事实会导致状态更改相继发生,从而导致 3 次重新渲染。如果函数是同步的并且仅导致一次重新渲染,为什么 React 将这些状态更改集中在一起?

您可以使用此 CodeSandBox来体验该行为。

Nic*_*wer 30

在react 17中,如果代码执行在react内部开始(例如,onClick侦听器或a useEffect),那么react可以确保在完成所有状态设置后,执行将返回到react并且可以从那里继续。因此对于这些情况,它可以让代码继续执行,等待返回,然后同步执行单个渲染。

但是,如果代码执行随机开始(例如,在 a 中setTimeout或通过解决承诺),那么当您完成时代码将不会返回做出反应。所以从 React 的角度来看,它正在安静地睡觉,然后你调用setState,迫使 React 表现得像“啊!他们正在设置状态!我最好渲染”。有一些异步方法可以让 React 等待看看您是否正在执行更多操作(例如,超时 0 或微任务),但是没有同步方法可以让 React 知道您何时完成。

您可以使用以下命令告诉 React 批量进行多个更改unstable_batchedUpdates:

import { unstable_batchedUpdates } from "react-dom";

const handleClickAsync = () => {
  setTimeout(() => {
    unstable_batchedUpdates(() => {
      setValue("two");
      setIsCondition(true);
      setNumber(2);    
    });
  });
};
Run Code Online (Sandbox Code Playgroud)

在版本 18 中,这是不必要的,因为他们对并发渲染的渲染所做的更改使得批处理适用于所有情况。


jmk*_*jmk 19

现在仅对setState事件处理程序内的批量同步做出反应。setTimeout但在 React 18 中,它将在、 s 等中可用useEffect,这是 Dan 的精彩解释https://github.com/reactwg/react-18/discussions/21