为什么 React useState hook 在执行剩余代码之前触发重新渲染?

use*_*452 6 javascript reactjs use-state

我试图弄清楚当使用 useState 钩子更新 React 中的状态时重新渲染何时发生。在下面的代码中,单击按钮会触发包含 setTimeout 的handleClick 函数。setTimeout 内部的回调会在 1 秒后执行,通过调用 setCount 来更新状态变量 count。然后控制台日志会打印一条消息。

单击按钮后我期望控制台日志显示的顺序是:

  1. '更新前计数', 0
  2. '在 setTimeout 中计算更新后的数量', 0
  3. '渲染计数', 1

然而,运行此代码后我看到的顺序是:

  1. '更新前计数', 0
  2. '渲染计数', 1
  3. '在 setTimeout 中计算更新后的数量', 0

“'Count in render', 1”怎么会出现在“'Count post update in setTimeout', 0”之前?setCount 不会导致重新渲染的安排不是立即进行吗?控制台在 setCount 函数调用后立即记录不应该总是在触发重新渲染之前执行吗?

function AppFunctional() {
  const [count, setCount] = React.useState(0);
  const handleClick = () => {
    console.log('Count before update', count);
    setTimeout(() => {
      setCount(count + 1);
      console.log('Count post update in setTimeout', count);
    }, 1000);
  };
  console.log('Count in render', count);
  return (
    <div className="container">
      <h1>Hello Functional Component!</h1>
      <p>Press button to see the magic :)</p>
      <button onClick={handleClick}>Increment</button>
      {!!count && (
        <div className="message">You pressed button {count} times</div>
      )}
    </div>
  );
}

ReactDOM.render(<AppFunctional />, document.querySelector('.react'));
Run Code Online (Sandbox Code Playgroud)
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>
Run Code Online (Sandbox Code Playgroud)

Cer*_*nce 7

setCount 不会导致重新渲染的安排不是立即进行吗?控制台在 setCount 函数调用后立即记录不应该总是在触发重新渲染之前执行吗?

在幕后,React通过在确定这样做是安全时对重新渲染进行排队和批处理来优化重新渲染。当在 React 理解的函数中(例如功能组件或钩子回调)时,如果您在这些函数之一中调用状态设置器,React 将知道延迟状态更新直到其处理完成是安全的 - 例如,直到所有效果/备忘录/等回调都已运行,并且直到原始状态的所有组件都已绘制到屏幕上。

但是,当您在内置 React 函数之外调用状态更新时,React 对其行为了解不够,无法知道如果延迟了状态更新,下次何时能够重新渲染。该setTimeout调用不是从 React 生命周期内部调用的,因此批量更新和优化它们要困难得多 - 因此,React 不会尝试猜测如何安全且异步地完成它,而是立即重新渲染。

  • 立即,如`setCount(count + 1); console.log(...` 在下一行的 `console.log` 之前重新渲染。当有渲染时,`Count in render` 日志。因此,它会记录在挂载上,并且每当单击按钮时(之后延迟),因为按钮单击调用状态设置器,并且状态更改会导致重新渲染。 (2认同)