在 React 中,setState 是同步,还是异步在不同的版本有不同的表现。

React 16

setState 在不同的情况下可以表现为异步或同步。

在 Promise 的状态更新、JS 原生事件,setTimeout、setInterval 中是同步的。

在 React 的合成事件中,是异步的。

原因

在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 state 还是放到队列中回头再说,而 isBatchingUpdates 默认是 false,也就表示 setState 会同步更新 state。

但是,有一个函数 batchedUpdates,这个函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会调用这个 batchedUpdates,造成的后果,就是由 React 控制的事件处理过程 setState 不会同步更新 state。

// ReactFiberScheduler.js
let isBatchingUpdates = false;

function batchedUpdates(fn, a) {
  const previousIsBatchingUpdates = isBatchingUpdates;
  isBatchingUpdates = true;
  try {
    return fn(a);
  } finally {
    isBatchingUpdates = previousIsBatchingUpdates;
    if (!isBatchingUpdates && !isRendering) {
      performSyncWork();
    }
  }
}

React 18

setState 都会表现成异步(即批处理)

可以查看官方的讨论 Automatic batching for fewer renders in React 18

批量更新

在 react 里面一个典型的例子就是连续设置 state:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button
        onClick={() => {
          setNumber(number + 1);
          setNumber(number + 1);
          setNumber(number + 1);
        }}
      >
        +3
      </button>
    </>
  );
}

虽然调用 setNumber(number + 1) 三次,但是每次事件处理中 number 总是 0,相当于设置了 number1 设置了三次。所以最终渲染的时候 number1 而非 3

在一次事件中去多次更新 state,可以使用 setNumber(n => n + 1) 的更新函数。

参考链接