React
setState 的同步与异步
setState 是同步还是异步,取决于执行上下文,而不是 setState 本身。
一、React 18:自动批处理
React 18 引入自动批处理(Automatic Batching),在以下上下文中,多次 setState 会合并为一次渲染:
- React 事件处理函数(
onClick、onChange等) - 生命周期函数
- 自定义 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可以打破批处理;函数式更新可以避免闭包旧值。