React Hooks:为什么异步函数中的多个useState setter会触发多次重渲染?
React 异步回调中 useState 更新不合并的原因
现象复现
同步更新场景(仅1次重渲染)
const handleClickSync = () => { // setter的执行顺序不影响结果,React会将所有状态更新合并 // 最终仅触发一次重渲染 setValue("two"); setIsCondition(true); setNumber(2); };
React会将三次状态更新合并处理,仅触发1次重渲染。
异步更新场景(触发3次重渲染)
const handleClickAsync = () => { setTimeout(() => { // 在异步函数(此处为setTimeout)内部,setter的执行顺序会影响结果 setValue("two"); setIsCondition(true); setNumber(2); }); };
每个useState setter调用都会触发一次重渲染,且setter的调用顺序会影响每一次渲染拿到的状态值。
核心原理(适用于React 17及更早版本)
这个差异本质是React旧版本的批量更新机制的上下文限制导致的:
- React内部通过
isBatchingUpdates标识位控制是否开启批量更新:- 处理自带合成事件(如onClick)时,React会先将该标识设为
true,开启批量更新模式 - 该模式下所有同步执行的setter不会立刻触发重渲染,只会先存入全局更新队列
- 等整个事件回调执行完成,React会将标识改回
false,再统一处理队列里的所有更新,合并后仅触发一次重渲染
- 处理自带合成事件(如onClick)时,React会先将该标识设为
- 异步回调(setTimeout、Promise.then、网络请求回调等)的执行时机,晚于事件回调的执行完成时间:
- 此时
isBatchingUpdates已经被重置为false,批量更新模式已经关闭 - 每调用一次setter,React都会直接触发完整的重渲染流程,因此三次setter会触发三次重渲染
- 同时因为每次更新都是实时生效的,后调用的setter可以拿到前一次更新后的状态值,所以调用顺序会影响每轮渲染的状态结果
- 此时
版本差异与解决方案
- React 18及以上版本:引入了自动批处理特性,取消了批量更新的上下文限制,只要是同一轮事件循环中触发的setter,不管放在同步还是异步回调里,都会自动合并为一次重渲染,上述差异已经不存在。
- React 17及更早版本:如果需要手动合并异步回调中的更新,可以使用ReactDOM提供的
unstable_batchedUpdates方法包裹更新逻辑:import { unstable_batchedUpdates } from 'react-dom'; const handleClickAsync = () => { setTimeout(() => { // 手动开启批量更新,三个setter仅触发一次重渲染 unstable_batchedUpdates(() => { setValue("two"); setIsCondition(true); setNumber(2); }); }); };
内容的提问来源于stack exchange,提问作者Andru
相关产品推荐
相关产品推荐

