Published on

React - 批处理

React 的合并更新策略只适用于同一个事件循环中的多个 setState 调用

有这样一个场景, 可以想一下两个 demo 中 最后的 a 的值是多少

// demo1
setA((a) => a + 1)
setA((a) => a + 2)

// demo2
setA(a + 1)
setA(a + 2)

实际的结果是 demo1: a === 3, demo2: a === 2

去大概看了一下 useState 的实现原理

const useState = (initState) => {
  // 处理 hooks 链表 fiber.memoizedState
  // return [baseState, dispatch]
}
// 18之前: 这里的两次setState并没有批量处理 render两次
setTimeout(() => {
  setA((a) => a + 1)
  setA((a) => a + 2)
}, 1000)

// 18之后 自动批量处理 render一次
setTimeout(() => {
  setA(a + 1)
  setA(a + 2)
}, 1000)

V18 之前

SyntheticEvent: 如果在一个事件循环中调用了多个 setState 函数,则 React 会将这些更新操作合并为一个批量处理,并在下一个事件循环中一次性执行所有的更新。

由于 JavaScript 事件循环机制的限制,当使用 setTimeoutsetInterval 等异步函数进行状态更新时,React 无法将多个连续的 setState 调用合并为一个更新操作。每次调用 setState 都会触发一次重新渲染操作,从而导致不必要的性能开销。

这是因为 setTimeoutsetInterval 等异步函数在当前事件循环结束后才被执行,而此时合成事件已经被处理完毕,新的状态更新操作无法再被加入到合成事件中进行批量处理。

V18 之后

Scope-Based Batching: 基于 Fiber 架构和时间轮询机制进行高效的更新操作调度和控制。Scope-Based Batching 机制可以更加灵活地控制更新操作的顺序和优先级,同时避免了 SyntheticEvent 机制中的性能问题和不一致性问题。Scope-Based Batching 机制将更新操作分成两类:

  • 同步更新,即立即执行,而异步更新则放入更新队列中,等待下一个时间轮询周期触发批量处理。

  • 异步更新,Scope-Based Batching 机制提供了更细粒度的控制,并支持多个更新队列,以便更好地管理复杂的场景。