FumadocsZDecode
React

setState 的同步与异步

setState 是同步还是异步,取决于执行上下文,而不是 setState 本身。


一、React 18:自动批处理

React 18 引入自动批处理(Automatic Batching),在以下上下文中,多次 setState 会合并为一次渲染:

  • React 事件处理函数(onClickonChange 等)
  • 生命周期函数
  • 自定义 Hook
function handleClick() {
  setCount((c) => c + 1) // 不立即渲染
  setFlag((f) => !f) // 不立即渲染
  // 函数结束后统一触发一次渲染
}

二、同步执行的场景

在 React 无法接管的上下文中,setState 会同步触发渲染:

  • setTimeout / setInterval
  • 原生 DOM 事件监听器
  • Promise 回调
setTimeout(() => {
  setCount((c) => c + 1) // 立即触发渲染
  setFlag((f) => !f) // 再次触发渲染,共两次
}, 1000)

React 18 之前,上述场景也是同步的;React 18 通过 createRoot 启动后,setTimeout 等也会被批处理。


三、强制同步:flushSync

需要立即读取更新后的 DOM 时,用 flushSync 强制同步:

import { flushSync } from 'react-dom'

flushSync(() => {
  setCount((c) => c + 1)
})
// 执行到这里时 DOM 已更新

四、函数式更新

依赖当前 state 计算新值时,用函数式更新,避免批处理中读到旧值:

// 可能读到旧的 count
setCount(count + 1)

// 始终基于最新值
setCount((prev) => prev + 1)

记住这一条

React 能接管的地方批处理(异步),接管不到的地方同步;flushSync 可以打破批处理;函数式更新可以避免闭包旧值。

On this page