调用 setState 时会发生什么?

What happens when setState is invoked?

我正在学习 React,但很难理解正在发生的代码流。

这是我的 code:It 是一个功能性的 React 组件

function App() {
  const [x, setx] = useState(1);
  const [x1, setx1] = useState(1);
  const [x2, setx2] = useState(1);
  const [x3, setx3] = useState(1);
  const [x4, setx4] = useState(1);
  const [x5, setx5] = useState(1);

  console.log("out3");

  const bclick = () => {
    setx1(x1 + 1);
    setx2(x2 + 1);
    setx3(x3 + 1);
    setx4(x4 + 1);
    setx5(x5 + 1);
    setx(x + 1);
    console.log("out1");
    bclick2();
  };
  const bclick2 = () => {
    console.log("out2");
  };
  console.log("out4");

  return (
    <div className="App">
      {console.log("in")}
      <button onClick={bclick} />
    </div>
  );
}

点击按钮后 console.log() 的输出: 输出1 输出2 输出3 输出4 在


Q> 单击按钮后,将执行多个不同的 setState。他们会重新评估组件或功能链(bclick 和 bclick2)完成执行,然后重新评估 App 组件。 根据我的输出,我意识到首先执行函数链。 那么 setState 是这样工作的吗? 代码流是否会先完成(不管函数的数量)然后重新评估功能组件?

这与 React 批处理 setState 调用有关,以优化渲染数量。通常你不必担心。

setState 调用是 async。 React 将决定何时以及如何应用多个 setState 调用。

处理程序将始终在 React re-renders 之前完成 运行ning。这就是为什么您会在任何 re-renders.

之前看到 bclick2() 调用 运行ning

我觉得 React 总是会在单个 re-render 中批处理多个 setState 调用。但是你可以看到,如果你在 setTimeout 中包装多个 setState 调用,React 将 re-render 多次,因为它无法知道这些超时需要多长时间才能完成。例如,您可能正在调用 API。

function App() {

  console.log('Rendering App...');

  const [x, setx] = React.useState(1);
  const [x1, setx1] = React.useState(1);
  const [x2, setx2] = React.useState(1);
  const [x3, setx3] = React.useState(1);
  const [x4, setx4] = React.useState(1);
  const [x5, setx5] = React.useState(1);

  const bclick = () => {
    console.clear();
    console.log("From bclick (batched: single render)");
    setx1(x1 + 1);
    setx2(x2 + 1);
    setx3(x3 + 1);
    setx4(x4 + 1);
    setx5(x5 + 1);
    setx(x + 1);
    console.log("Calling bclick2");
    bclick2();
  };

  const bclick2 = () => {
    console.log("From bclick2");
  };
  
  const notBatched = () => {
    console.clear();
    console.log('From notBatched (multiple renders)');
    setTimeout(() => setx1((prevState) => prevState+1),0);
    setTimeout(() => setx1((prevState) => prevState+1),0);
    setTimeout(() => setx1((prevState) => prevState+1),0);
  };
  
  

  return (
    <div className="App">
      <button onClick={bclick}>Click (will batch)</button>
      <button onClick={notBatched}>Click (will not batch)</button>
    </div>
  );
}

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

例如,如果您从 useEffect 调用 API:

useEffect(() => {
  setIsLoading(true);             // 1
  const data = await fetchAPI();  
  setData(data);                  // 2
  setIsLoading(false);            // 3
},[]);

在这种情况下,React 将 运行 #1,然后,当 API 调用完成时,它将 运行 #2 和 #3 分开(未批处理)。不确定它为什么选择单独执行,因为将它们 运行 放在一起是安全的,但我确信 React 有它自己的原因。可能由于 API 调用而超时的整个块以某种方式标记为 shouldNotBatch。我实际上不知道他们为此使用的内部逻辑是什么。

const fetchAPI = () => {
  return new Promise((resolve) => {
    setTimeout(() => resolve('DATA'),1500);
  });
}

const App = () => {
  console.log('Rendering App...');
  const [isLoading,setIsLoading] = React.useState(false);
  const [data,setData] = React.useState(null);

  // I changed the `await` to `then` because SO snippets don't allow `await` in this case
  React.useEffect(() => {
    console.log('Running useEffect...');
    setIsLoading(true);             // 1
    fetchAPI().then((data) => {
      setData(data);                // 2
      setIsLoading(false);          // 3
    });;  
  },[]);

  return(
    <div>
      <div>isLoading:{JSON.stringify(isLoading)}</div>
      <div>data:{JSON.stringify(data)}</div>
    </div>
  );
};

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


React v18(2022 年 4 月)

显然 React v18 批处理也来自异步处理程序调用。

https://vogue.globo.com/celebridade/noticia/2022/04/bbb-portugal-bruna-gomes-e-pedida-em-namoro-por-bernardo-sousa.html