React 中状态更新的合并机制和 flushSync 的工作原理,本质上与 React 的更新调度机制和批量更新策略相关。我们可以从底层逻辑拆解:
一、同步状态更新被合并的原理:批量更新(Batching)
React 为了避免频繁渲染导致的性能问题,会将同一事件循环中的多个状态更新合并为一次渲染,这个过程称为“批量更新”。
具体逻辑:
更新队列(Update Queue)
当调用setState(或useState的更新函数)时,React 不会立即更新状态并触发渲染,而是将这次更新放入一个“更新队列”中。批量处理时机
React 会在当前代码执行上下文(如事件处理函数、useEffect回调等)执行完毕后,统一处理队列中的所有更新:- 先计算所有状态的最终值(比如多次更新同一状态时,取最后一次的结果;更新不同状态时,合并成一个新的状态对象)。
- 然后只触发一次组件渲染,使用合并后的最终状态。
为什么
useEffect中同步更新会被合并?useEffect的回调函数属于 React 可控制的“合成事件/生命周期上下文”,React 会在此类上下文中自动启用批量更新。因此,即使在useEffect中同步调用多次setXxx,也会被放入同一个更新队列,最终合并为一次渲染。
二、flushSync 做了什么?强制立即执行更新
flushSync 是 React 18 引入的 API,作用是打破批量更新机制,强制让当前更新队列中的所有更新立即执行,并同步触发渲染。
具体操作:
1.** 暂停批量更新 **:flushSync 会临时关闭 React 的批量更新开关,告诉 React“这次更新必须立即处理,不能等待”。
2.** 同步执行更新队列 **:flushSync 内部的状态更新会被立即加入更新队列,并强制 React 同步处理队列中的所有更新(包括之前可能积累的未处理更新)。
3.** 触发渲染 **:更新处理完成后,会立即触发组件渲染,确保状态的变化同步反映到 DOM 上。
4.** 恢复批量更新 **:执行完毕后,React 会恢复批量更新机制,不影响后续代码的默认行为。
举个形象的例子:
可以把 React 的批量更新比作“快递柜”:
- 平时,你(状态更新)会把快递(更新任务)放进柜子,快递员(React)会等柜子满了(当前上下文执行完)再一次性取走派送(合并渲染)。
- 而
flushSync相当于你直接给快递员打电话:“这个快递必须现在送!”,快递员会立即过来取走这个快递(以及柜子里可能有的其他快递),当场派送(同步渲染)。
关键区别总结:
| 场景 | 批量更新? | 渲染时机 | 适用场景 |
|---|---|---|---|
| 普通同步状态更新 | 是 | 当前上下文执行完后合并渲染 | 绝大多数情况,优化性能 |
flushSync 包裹的更新 | 否 | 立即执行并同步渲染 | 必须立即看到更新的场景(罕见) |
注意:
flushSync 会破坏 React 的性能优化,可能导致额外的渲染,甚至引发布局抖动(因为同步渲染会立即操作 DOM)。因此,只有在必须让状态更新同步生效的场景下使用(例如:需要立即读取更新后的 DOM 信息),其他情况应依赖 React 的默认批量更新。