React-Redux 的 batch() API:合并 React 渲染更新、优化多 action 批量分发实战指南
2026/9/20 14:03:30 网站建设 项目流程

React-Redux 的 batch() API:合并 React 渲染更新、优化多 action 批量分发实战指南

【免费下载链接】react-reduxOfficial React bindings for Redux项目地址: https://gitcode.com/gh_mirrors/re/react-redux

batch()是 React-Redux 自 v7.0.0 起公开导出的一个工具函数,其作用是把同一事件循环内、位于 React 事件系统之外的多次状态更新合并为一次渲染提交。本文以仓库中 v7.0 版 batch 文档 为主体骨架,结合当前仓库的源码实现(src/utils/batch.ts、src/exports.ts、src/utils/Subscription.ts)与测试用例,讲解batch()的由来、用法、内部实现与演进,帮助读者理解在 React 18 之前如何避免"一次分发多次渲染"的性能浪费,以及 React 18 之后该 API 为何退化为无操作(no-op)。

一、batch()是什么

batch()是 React-Redux 在 v7.0.0 中新增并公开导出的 API,函数签名如下:

batch(fn: Function)

它接收一个回调函数,并在调用期间把其中产生的所有 React 状态更新收集起来,合并为单次渲染提交。官方文档给出的定位非常明确:当你在 React 事件处理器之外分发多个 Redux action 时,用它来保证"多个 action 只引起一次渲染更新"。

import { batch } from 'react-redux' function myThunk() { return (dispatch, getState) => { // should only result in one combined re-render, not two batch(() => { dispatch(increment()) dispatch(increment()) }) } }

在这个 thunk 示例中,batch()回调内的两次dispatch(increment())虽然依次触发了两次 store 更新通知,但最终只会导致订阅组件被合并成一次重渲染,而不是两次。

二、为什么要设计batch():React 批处理机制的背景

要理解batch()的价值,需要先了解 React 的批处理(batching)机制。React 的unstable_batchedUpdates()API 允许把同一事件循环 tick 内的所有 React 更新合并到单次渲染提交中。关键事实是:

  • React内部已经在自己的事件处理回调中对更新做了批处理;
  • 但该 API 属于renderer 包(如 ReactDOM、React Native),而非 React 核心本身。

也就是说,在 React 18 的自动批处理(automatic batching)出现之前,批处理只发生在 React 自己管理的事件路径上。对于 Redux 这类"外部状态源"来说,常见的非 React 路径包括:

  • setTimeout/setInterval回调;
  • Promise 的.then()async/await后续代码;
  • 原生 DOM 事件监听器;
  • WebSocket / SSE 消息回调;
  • Redux-thunk、Redux-Saga 等中间件中异步触发的连续分发。

在这些路径里连续dispatch多个 action,React 18 之前的 ReactDOM 会为每次 store 更新触发一次同步渲染,导致多余的重渲染开销。这正是 React-Redux 暴露batch()的直接原因。

三、React-Redux 如何把batch()提供给用户

由于 React-Redux 需要同时运行在 ReactDOM 和 React Native 两种环境下,而批处理 API 又位于各自的 renderer 包中,React-Redux 在构建时负责从正确的 renderer 导入该 API 供内部使用,并把它重命名为batch()对外公开。

这一点可以从仓库的 API 报告文件 etc/react-redux.api.md 中得到佐证,其顶部对batch的来源声明为:

import { unstable_batchedUpdates as batch } from 'react-dom'

也就是说,在 v7 时代,batch本质上就是 ReactDOM 的unstable_batchedUpdates的再导出——它在构建期被固定绑定到正确的 renderer 实现,从而屏蔽了 ReactDOM 与 React Native 之间的差异。

同时,batchProviderconnectuseSelector等一起被列在包的主入口导出清单中,见 src/exports.ts:

const batch = defaultNoopBatch export { Provider, batch, connect, legacy_connect, shallowEqual }

四、源码视角:batch()在 React-Redux 内部的真实用法

batch()不只是给用户用的工具,它也是 React-Redux 订阅通知机制的核心环节。在 src/utils/Subscription.ts 中,订阅模块把defaultNoopBatchbatch的别名导入,并用它包裹整条监听器通知链:

import { defaultNoopBatch as batch } from './batch' // ... notify() { batch(() => { let listener = first while (listener) { listener.callback() listener = listener.next } }) },

这段代码的含义是:当 store 状态变化时,Subscription会遍历所有已订阅的监听器(对应各个connect组件或 hooks 消费者),而整轮遍历被放在batch()回调内执行。这样,同一轮 store 更新触发的所有组件通知会在一个批处理单元内完成,从而保证祖先组件先于后代组件重渲染(订阅模块注释中明确写明了这一自上而下的更新保证),并让多个被通知组件共享一次渲染提交。

测试代码也印证了这一内部行为。在 test/components/Provider.spec.tsx 中,测试注释直接写道 "Provider uses unstable_batchedUpdates() under the hood"(Provider 底层使用了unstable_batchedUpdates()),并在rtl.act()中连续分发 action 后断言子组件只被调用一次。此外,test/components/connect.spec.tsx 中也有 "setState calls DOM handlers are batched"(DOM 事件处理器中的 setState 调用会被批处理)的断言。

五、演进:从unstable_batchedUpdates到 no-op

值得特别说明的是,当前仓库(v9.x)中的batch已经是一个立即执行回调的 no-op 实现,其定义见 src/utils/batch.ts:

// Default to a dummy "batch" implementation that just runs the callback export function defaultNoopBatch(callback: () => void) { callback() }

src/exports.ts中也对batch标注了明确的弃用说明(见 src/exports.ts):

/** * @deprecated As of React 18, batching is enabled by default for ReactDOM and React Native. * This is now a no-op that immediately runs the callback. */ const batch = defaultNoopBatch

这一演进对应 React 18 的自动批处理(automatic batching)特性:React 18 起,无论更新在何处排队,ReactDOM 与 React Native 都会自动批量处理所有状态更新。因此,当前版本官方文档 docs/api/batch.md 明确提示:

如果你正在使用 React 18,你不需要使用batchAPI。React 18 会自动批处理所有状态更新,无论它们在何处排队。

换句话说,batch()的历史使命是弥补 React 18 之前"React 事件系统之外不自动批处理"的缺口;React 18 之后该缺口被框架本身填平,batch()保留下来仅仅是为了向后兼容——调用它只会立即执行回调,不会产生额外效果。需要说明的是,上述"自动批处理"能力来源于 React 18 的官方设计,而 React-Redux 侧以 no-op 承接兼容性,这一事实以 src/utils/batch.ts 与 src/exports.ts 的代码为准。

六、实用指南:什么场景还需要(或不需要)batch()

综合上述演进,可以给出如下使用结论:

React 18(含 createRoot)之前的环境:

  • 在 React 事件处理器、生命周期之外连续分发多个 action(如 thunk 内的连续 dispatch、定时器回调、异步请求完成后的多次 dispatch),建议用batch(() => { ... })包裹,把多次渲染合并为一次;
  • 示例即本文第一节的 thunk 代码,注释明确写出"应该只产生一次合并重渲染,而不是两次"。

React 18 及之后的环境:

  • 不需要手动调用batch(),框架已自动批处理;
  • 调用batch()也不会出错,它只是立即执行回调(no-op),可作为过渡期代码保持兼容。

边界与限制:

  • 当前仓库为 React Server Components 提供了独立入口 src/index-rsc.ts,其中batchProviderconnect、hooks 等一样被替换为throwNotSupportedError——在 RSC 环境中调用batch会直接抛出错误并提示"此导出仅支持在 Client Component 中使用";
  • batch()只合并同一回调内排队的更新;跨回调(如两个独立的setTimeout)的更新不会被合并;
  • 包的依赖与运行环境前提见 package.json 的peerDependenciesreact: ^18.0 || ^19redux: ^5.0.0),并结合 tsup.config.ts 中多目标构建产物理解其在不同模块格式下的分发方式。

七、延伸阅读指引

原文档 v7.0 版 batch 文档 末尾附有两条参考来源,此处以文字形式说明其内容要点(不在文章中嵌入外部链接):

  • React 官方关于unstable_batchedUpdates()API 的引入提交,是理解该 API 设计意图的第一手资料;
  • React 18 工作组关于"Automatic Batching for Fewer Renders"(自动批处理、更少渲染)的公开讨论(编号 #21),详细阐述了 React 18 如何把批处理扩展到所有更新场景,也是batch()退化为 no-op 的官方背景。

仓库内同一主题的更多版本资料还包括 v7.1 版 batch 文档、v7.2 版 batch 文档 以及当前的 docs/api/batch.md,内容一致,可对照阅读以了解该 API 在版本演进中的措辞变化(如 v7.1 文档中的unstable_batchedUpdate()unstable_batchedUpdates()拼写差异,即属历史版本遗留)。

总结

batch()是 React-Redux 为应对 React 18 之前"React 事件系统之外不自动批处理"这一限制而提供的官方工具:它把unstable_batchedUpdates从正确的 renderer 在构建期导入并公开重导出,内部用它包裹订阅通知循环以保证批量渲染,外部用它帮助用户在 thunk、异步回调等场景合并多次 dispatch 引发的渲染。到了 React 18 的自动批处理时代,它按设计退化为立即执行回调的 no-op,以最小代价保持 API 兼容。理解这条从"必要工具"到"兼容占位"的演进线,既能写出更高效的 React-Redux 代码,也能准确判断新老版本项目各自的最优实践。

【免费下载链接】react-reduxOfficial React bindings for Redux项目地址: https://gitcode.com/gh_mirrors/re/react-redux

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询