ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/9/18 7:41:26 拓冰建站 浏览量
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),仅供参考