Redux `compose(...functions)` 完全指南:从右向左的函数组合与 Store Enhancer 应用

发布时间:2026/9/18 7:41:27

Redux `compose(...functions)` 完全指南:从右向左的函数组合与 Store Enhancer 应用 Reduxcompose(...functions)完全指南从右向左的函数组合与 Store Enhancer 应用【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/reduxcompose是 Redux 核心包自带的一个函数式编程工具用于从右向左组合多个函数将f(g(h(...args)))这类深层嵌套调用展平为compose(f, g, h)(...args)。在 Redux 中最典型的应用场景是当使用createStore时需要一次性传入多个 store enhancer如applyMiddleware与 DevTools 的instrument()时用compose将它们合并为单一 enhancer。本文基于 Redux 仓库中的 compose API 文档结合 compose 源码实现、单元测试 与 TypeScript 类型测试完整讲解其参数约定、返回值、边界行为与底层原理。概述什么是compose(...functions)compose把多个单参数函数按从右到左的顺序组合成一个函数最右侧的函数最先执行其返回值依次作为左侧函数的入参。它本身不引入任何新逻辑只是把函数嵌套调用改写为管道式的调用写法从而避免代码向右无限缩进rightward drift。在 Redux 中它的主要用途是连续应用多个 store enhancer。一个 store enhancer 是高阶函数签名形如createStore createStore更完整的类型定义见 src/types/store.ts用于以可组合的方式增强 store 的能力例如增加中间件、实现时间旅行调试等。createStore只接受一个enhancer 参数因此当你有多个 enhancer 时就必须用compose把它们合并。import { createStore, applyMiddleware, compose } from redux import { thunk } from redux-thunk import DevTools from ./containers/DevTools import reducer from ../reducers const store createStore( reducer, compose(applyMiddleware(thunk), DevTools.instrument()) )此外compose也是一个完全独立的通用工具函数可以脱离 Redux 单独使用例如组合数据处理管道、装饰器等。:::warning 注意在日常开发中你不必手动调用compose。Redux Toolkit 的configureStore方法会自动配置好标准的applyMiddleware与 Redux DevTools store enhancer并通过enhancers参数接收额外 enhancer。本文所述的手动组合方式主要用于理解 Redux 核心原理或维护仍基于createStore的遗留代码库。:::参数Argumentscompose(...functions)接受任意数量的函数作为参数规则如下每个函数预期接收单个参数。函数的返回值会作为其左侧函数的入参依次向左传递。唯一的例外是最右侧的函数它可以接收多个参数因为它为最终组合出来的函数提供了完整签名——最终调用compose(f, g, h)(a, b, c)时参数(a, b, c)会先全部传给最右侧的h再由g接收h的返回值最后由f接收g的返回值。这一约定在源码注释中有明确表述见 src/compose.tscompose(f, g, h)等价于(...args) f(g(h(...args)))。返回值ReturnsFunction将给定函数从右向左组合后得到的最终函数。组合结果函数的调用签名由最右侧函数的签名决定。实现原理源码逐层拆解compose的运行时实现非常精简完整逻辑位于 src/compose.tsexport default function compose(...funcs: Function[]) { if (funcs.length 0) { // 空参数返回恒等函数 return T(arg: T) arg } if (funcs.length 1) { // 单参数原样返回该函数 return funcs[0] } return funcs.reduce( (a, b) (...args: any) a(b(...args)) ) }三个分支的边界行为零个函数返回恒等函数arg arg即compose()(value)原样返回value。源码注释说明这是为了在后续类型推断中保留下行参数类型。一个函数直接返回该函数本身不做任何包装。测试 test/compose.spec.ts 验证了compose(fn)与fn是同一引用。多个函数使用Array.prototype.reduce从右向左折叠。关键在于 reduce 的折叠方向[f, g, h].reduce((a, b) (...args) a(b(...args)))的执行过程是——第一步得到(...args) f(g(...args))第二步再与h组合得到(...args) f(g(h(...args)))。最终结果是h最先执行这与从右向左组合的语义完全一致。类型层面的重载设计为了在编译期精确推导组合结果的类型compose在 src/compose.ts 中声明了多组函数重载逐一覆盖从 0 到 4 个函数的组合以及 rest 参数的兜底签名compose(): R(a: R) R零参数时的恒等函数composeF extends Function(f: F): F单参数时保持原类型两个至四个函数泛型按A → B → C → R的链条逐级约束例如三函数签名f1: (b: B) R, f2: (a: A) B, f3: FuncT, Arest 兜底f1: (a: any) R, ...funcs: Function[]。类型测试 test/typescript/compose.test-d.ts 覆盖了这些场景例如compose(numberToString, numberToNumber)(5)被推导为stringcompose(stringToNumber, numberToString, numberToNumber)(5)被推导为number多参数函数放在最右侧时如multiArgFn组合结果仍可接收(bar, 42, true)三个实参。实战用法一组合 store enhancer这是compose在 Redux 中最核心的用途。createStore的签名支持createStore(reducer, preloadedState?, enhancer?)见 src/createStore.ts当检测到 enhancer 时会以enhancer(createStore)(reducer, preloadedState)的方式交给 enhancer 处理src/createStore.ts。注意createStore只接受一个 enhancer如果直接传入多个 enhancer 会抛出运行时错误It looks like you are passing several store enhancers to createStore(). This is not supported. Instead, compose them together to a single function.见 src/createStore.ts——这正是compose存在的意义。官方文档示例 docs/api/compose.md 展示了标准组合方式import { createStore, applyMiddleware, compose } from redux import { thunk } from redux-thunk import DevTools from ./containers/DevTools import reducer from ../reducers const store createStore( reducer, compose(applyMiddleware(thunk), DevTools.instrument()) )这里compose把两个 enhancer 合成一个DevTools.instrument()包裹applyMiddleware(thunk)的结果最终返回一个统一的 enhancer 传给createStore。顺序很重要中间件必须放在前面applyMiddleware的文档docs/api/applyMiddleware.md明确建议如果除了applyMiddleware还有其他 enhancer务必把applyMiddleware放在组合链的前面即更靠右的位置先执行因为中间件可能是异步的。若将 DevTools 放在applyMiddleware之前DevTools 将看不到 Promise 等异步中间件产生的原始 action。条件性应用中间件的写法当你需要按环境条件加载中间件时可以结合展开运算符与composelet middleware [a, b] if (process.env.NODE_ENV ! production) { const c require(some-debug-middleware) const d require(another-debug-middleware) middleware [...middleware, c, d] } const store createStore( reducer, preloadedState, applyMiddleware(...middleware) )这种方式便于打包工具如 webpack在 production 构建中剔除未使用的模块减小产物体积。实战用法二中间件链的底层组装compose不仅在用户代码中出现也是 Redux 内部实现的核心。看 applyMiddleware 的源码export default function applyMiddleware( ...middlewares: Middleware[] ): StoreEnhancerany { return createStore (reducer, preloadedState) { const store createStore(reducer, preloadedState) // ... const chain middlewares.map(middleware middleware(middlewareAPI)) dispatch composetypeof dispatch(...chain)(store.dispatch) // ... } }中间件形如({ getState, dispatch }) next action见 docs/api/applyMiddleware.md 的参数说明调用后生成next action形式的函数链。applyMiddleware用compose(...chain)把这串函数从右向左组合把最内层的store.dispatch作为最右侧的next传入——这正是 test/compose.spec.ts 中composes functions from right to left测试所验证的模式compose(a, b, c)(final)()的结果为abc证明final最先被调用、a最后执行即后列出的中间件更靠近真实 dispatch先列出的中间件最先处理 action。因此compose本质上承担着中间件洋葱模型的装配职责action 从最左侧中间件进入逐层向里传递最终到达 store 的原始dispatch返回值再沿原路径向外返回。实战用法三通用函数组合作为通用工具compose可以直接组合数据处理函数例如import { compose } from redux const double x x * 2 const square x x * x // 等价于 square(double(5)) 100 compose(square, double)(5) // 100 // 等价于 double(square(double(5))) 200 compose(double, square, double)(5) // 200测试 test/compose.spec.ts 覆盖了这类场景。需要注意的是运行时对参数类型没有强校验若某个位置传入非函数会在调用组合结果时抛出TypeError测试 test/compose.spec.ts 验证了传入false、undefined、true、NaN、42等值会抛错但在 TypeScript 中这些错误用法会在编译期被类型系统拦截。多参数种子最右侧函数接收全部实参当最右侧函数接收多个参数时组合结果的签名随之扩展。例如测试 test/compose.spec.tsconst square (x, _) x * x const add (x, y) x y compose(square, add)(1, 2) // (1 2) ** 2 9这里add(1, 2)接收全部实参square接收其返回值最终结果是 9。这个最右侧函数决定组合签名的特性与中间件链中store.dispatch作为最右侧接收单个 action 的场景形成对照。常见错误与排查给createStore传多个 enhancer直接写createStore(reducer, enhancerA, enhancerB)会触发 src/createStore.ts 中的运行时错误正确做法是用compose(enhancerA, enhancerB)合并成一个。把多参数函数放在非最右侧位置compose只保证最右侧函数可接收多个参数中间的每个函数都只接收其右侧函数的单个返回值。若中间函数本身需要多参数属于用法错误TypeScript 重载会在编译期报错。空参数调用compose()返回恒等函数compose()(3)仍得到 3、compose()(undefined)仍得到undefined见 test/compose.spec.ts可用于需要可选的组合的场景。Tips正确看待compose正如 官方文档 所强调的compose的全部作用只是让你不用写出层层嵌套的函数变换从而避免代码向右漂移。它没有引入任何新的运行时语义——compose(f, g, h)(x)与f(g(h(x)))完全等价。因此不必对它抱有过高期待把它当作一个语法层面的便利工具即可。延伸阅读在 store enhancer 语境下理解compose的用途可阅读 术语表store enhancer 以及 Store API 文档想了解compose组合的对象——applyMiddleware可阅读 applyMiddleware 文档想了解 enhancer 的入口——createStore的参数与校验逻辑可阅读 createStore 文档Redux 顶层导出清单见 API Referencecompose与其他核心 APIcreateStore、combineReducers、applyMiddleware、bindActionCreators一同从包入口 src/index.ts 导出源码与测试参考compose 实现、compose 单元测试、compose 类型测试、applyMiddleware 中的 compose 调用。【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/9/18 7:41:27

论文降AI率工具对比与学术写作优化实践

1. 论文降AI率工具的市场需求去年帮导师审研究生论文时发现一个现象:超过60%的初稿都存在明显的AI写作痕迹。最典型的特征是段落结构过于工整、术语堆砌但缺乏逻辑衔接、参考文献格式整齐得不像人工整理。这让我意识到,随着AI写作工具的普及,…

2026/9/18 7:41:27

完整重复文件清理神器 Czkawka:给硬盘瘦身

完整重复文件清理神器 Czkawka:给硬盘瘦身 【免费下载链接】czkawka Multi functional app to find duplicates, empty folders, similar images etc. 项目地址: https://gitcode.com/GitHub_Trending/cz/czkawka 年度手机备份之后,你发现照片库里…

2026/9/18 7:36:27

Linux入门指南:从虚拟机搭建到终端命令实战

1. Linux系统初探:从零开始的数字世界漫步第一次接触Linux时,我被终端里闪烁的光标和神秘的命令行震撼了。这个诞生于1991年的开源操作系统,如今已渗透到我们数字生活的每个角落——从智能手机到超级计算机,从智能家电到金融交易系…

2026/9/18 8:41:32

KWDB:面向AI应用的多模统一数据库底座

1. 这不是又一个“拼凑式AI应用”,而是一次数据库层的底层重构“受够了AI应用里的‘缝合怪’架构”——这句话我读到第一遍就停住了。不是因为情绪化,而是因为它精准戳中了过去三年我在十几个AI项目里反复踩坑的核心痛点:模型调得再好&#x…

2026/9/18 8:41:32

SpringBoot+Vue小区团购管理系统开发实战

1. 项目背景与核心价值小区团购管理系统是近年来社区电商快速发展的产物。去年我在参与某大型社区数字化转型项目时,发现传统微信群接龙式的团购管理存在诸多痛点:订单统计混乱、支付对账困难、配送效率低下。这促使我开发了这套基于SpringBootVue的全栈…

2026/9/18 8:41:32

Matlab实现光伏配电网空调智能调控方案

1. 项目背景与核心价值去年夏天参与某工业园区微电网项目时,我亲眼目睹了空调负荷突增导致变压器过载跳闸的故障。现场工程师们手忙脚乱地关闭非关键设备时,我突然意识到:在可再生能源渗透率越来越高的今天,传统"以需定供&qu…

2026/9/18 8:36:31

仓库面积计算指南:从托盘位反推到Python可调参模型

简介:一份聚焦仓库面积计算原则与实践方法的专业资料,适合物流管理、仓储运营及相关专业学习者使用。内容从基本计算公式出发,通过两个核心实例详细讲解货位面积估算与配送中心储存能力核算:前者演示了如何依据仓容定额与货物总量…

2026/9/16 12:52:37

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/18 0:01:09

Google Colab 实战:运行模型、数据加载与报错排查

1. 为什么我劝你先搞懂 Colab 的运行模型1.1 Colab 到底是什么,跟本地跑代码差在哪Google Colab 简单说就是一台跑在浏览器里的 Linux 虚拟机,你打开一个 Notebook,背后就连上了一台带 GPU 的远程机器。你在单元格里敲的每一行 Python&#x…

2026/9/18 0:01:09

C语言数据类型与表达式详解

1. C语言数据与数据类型概述在C语言编程中,数据是程序处理的核心对象。理解数据的分类和特性是掌握C语言的基础。C语言中的数据主要分为四大类:常量、变量、表达式和函数。这些数据类型构成了C语言程序的基本元素,每种类型都有其独特的特性和…

2026/9/18 0:01:09

SQL时间字段指定时间段查询:区间语义、索引与时区避坑

上周排查一个线上问题&#xff0c;用户反馈"昨天的订单一条都没查到"&#xff0c;但数据库里明明躺着两千多条。最后定位下来&#xff0c;不是数据丢了&#xff0c;也不是接口挂了&#xff0c;而是那个查询条件把时间段写成了> 2024-05-20 00:00:00 AND < 2024…

2026/9/16 22:55:57

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/16 22:56:09

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/16 22:56:16

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码