编写 Redux 自定义中间件:从标准模式到源码级兼容性实践
编写 Redux 自定义中间件从标准模式到源码级兼容性实践【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux导读中间件Middleware是 Redux 在派发 action与action 到达 reducer之间提供的第三方扩展点用于为 action 创建副作用、修改或取消 action以及扩展dispatch所能接受的输入类型。本文以 Redux 官方文档《Writing Custom Middleware》为主线结合当前仓库 applyMiddleware 源码 与 类型定义 的底层实现系统讲解自定义中间件的三大用途、标准编写模式以及与 redux-thunk、RTK Query 等生态中间件协同时必须遵守的两条兼容性规则。读完本文你将能写出可投入生产、与现有中间件链无缝协作的自定义中间件。中间件能做什么三种典型用途Redux 中的中间件主要用来做三类事情为 action 创建副作用最常见例如 [Redux-Saga]、[redux-observable] 以及 [RTK listener middleware] 都属于这一类——它们对 action 做出反应执行状态变更之外的额外逻辑。修改或取消 action例如用状态或外部输入为 action 附加信息或者对 action 做节流throttle、防抖debounce、门控gate。修改dispatch接受的输入最典型的例子是 [Redux Thunk]它把返回 action 的函数通过调用该函数转换为真正的 action。注上文提及的第三方库链接仅作为概念参照本文的代码与实现细节均以当前仓库源码为准。什么时候才需要自己写中间件大多数情况下你并不需要自定义中间件。副作用是中间件最常见的用途而生态中已有大量经过长期验证的成熟方案自己从零实现容易踩到各种隐蔽问题。建议优先考虑现成方案管理服务端状态数据获取/缓存使用 [RTK Query]其他副作用使用 [RTK listener middleware]。只有在以下两种场景才建议手写中间件只有一个非常简单的副作用为一个副作用引入整套框架可能不划算。此时可以写一个轻量中间件但务必记住当应用规模增长后要切换到成熟框架而不是继续养大自己的自定义方案。需要修改或取消 action这类需求没有通用的现成方案通常必须自定义。中间件的标准模式Standard Patterns为 action 创建副作用以 RTK listener middleware 为例这是最常见的中间件形态。其标准骨架是三层嵌套函数外层接收{ dispatch, getState }MiddlewareAPI中层接收next内层接收action。以 RTK listener middleware 的核心逻辑为例const middleware: ListenerMiddlewareS, D, ExtraArgument api next action { // 第一部分处理 addListener / clearAllListeners / removeListener 这类管理型action // 借此增删后续要触发的监听器 if (addListener.match(action)) { return startListening(action.payload) } if (clearAllListeners.match(action)) { clearListenerMiddleware() return } if (removeListener.match(action)) { return stopListening(action.payload) } // 第二部分在 reducer 处理 action 之前先取一次原始状态 // 注意必须在 next(action) 之前同步调用 let originalState: S | typeof INTERNAL_NIL_TOKEN api.getState() // getOriginalState 只能在同步代码中调用 const getOriginalState (): S { if (originalState INTERNAL_NIL_TOKEN) { throw new Error( ${alm}: getOriginalState can only be called synchronously ) } return originalState as S } let result: unknown try { // 先把 action 沿中间件链继续向下传、交给 reducer 处理 result next(action) // reducer 执行完之后再取新状态然后通知各监听器 if (listenerMap.size 0) { let currentState api.getState() const listenerEntries Array.from(listenerMap.values()) for (let entry of listenerEntries) { let runListener false try { runListener entry.predicate(action, currentState, originalState) } catch (predicateError) { runListener false safelyNotifyError(onError, predicateError, { raisedBy: predicate }) } if (!runListener) { continue } notifyListener(entry, action, api, getOriginalState) } } } finally { // 清除作用域内的 originalState 引用 originalState INTERNAL_NIL_TOKEN } return result }这段代码体现了两个要点第一部分next(action)之前监听addListener、clearAllListeners、removeListener等命令型action用来动态增删后续要运行的监听器。第二部分next(action)之后先通过api.getState()取到 action 经过其他中间件和 reducer 之后的新状态再把原始状态和新状态一并交给监听器使用。为什么副作用通常放在next(action)之后执行因为这样能同时拿到原始状态与新状态做对比而且副作用产生的交互本就不该影响当前 action 的执行——否则它就不叫副作用了。修改/取消 action或扩展dispatch输入以 redux-thunk 为例这一类模式相对少见但除取消 action外绝大部分能力都能在 redux-thunk 中间件中看到。redux-thunk 的完整实现只有几行const middleware: ThunkMiddlewareState, BasicAction, ExtraThunkArg ({ dispatch, getState }) next action { // thunk 中间件检查传给 store.dispatch 的是否是函数。 // 如果是函数即 thunk就调用它并返回其结果。 if (typeof action function) { // 注入 store 的 dispatch、getState 方法以及可选的 extra arg return action(dispatch, getState, extraArgument) } // 否则把 action 沿中间件链正常向下传递 return next(action) }原理说明原生dispatch只能处理普通对象JSON 可序列化actionthunk 中间件让dispatch额外接受函数形态的 action。它还改变了dispatch的返回类型把函数 action 的返回值作为dispatch的返回值返回因此调用方可以await一个返回 Promise 的 thunk。注意这里注入的是dispatch而非nextthunk 内部的dispatch会重新从中间件链的起点开始派发从而支持在异步流程中继续派发其他 action。与仓库源码互相印证applyMiddleware如何组装链条要理解上面两种模式为什么成立需要看 Redux 内置的 applyMiddleware 源码export default function applyMiddleware( ...middlewares: Middleware[] ): StoreEnhancerany { return createStore (reducer, preloadedState) { const store createStore(reducer, preloadedState) let dispatch: Dispatch () { throw new Error( Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch. ) } const middlewareAPI: MiddlewareAPI { getState: store.getState, dispatch: (action, ...args) dispatch(action, ...args) } const chain middlewares.map(middleware middleware(middlewareAPI)) dispatch composetypeof dispatch(...chain)(store.dispatch) return { ...store, dispatch } } }关键机制如下每个中间件只被调用一次middleware(middlewareAPI)得到包装函数即中层函数真正的包装链通过 compose 源码 从右到左组合而成。中间件拿到的是{ dispatch, getState }子集而不是整个 store参见 types/middleware.ts 中MiddlewareAPI的定义这与仓库 types/store.ts 中dispatch/getState的真实签名一致。通过闭包dispatch: (action, ...args) dispatch(action, ...args)实现了从中间件内部调用dispatch会重新走完整条链含当前中间件的行为——这正是 thunk 等异步中间件所依赖的特性也是 test/applyMiddleware.spec.ts 中passes recursive dispatches through the middleware chain测试验证的内容。在中间件构造期间派发 action 是被禁止的此时dispatch指向一个会抛错的占位函数错误信息见上文源码第 58-63 行因为此时组装尚未完成其他中间件尚未生效。对应测试见 test/applyMiddleware.spec.tswarns when dispatching during middleware setup。仓库中 test/helpers/middleware.ts 也提供了一个极简 thunk 中间件实现可作为自定义中间件的最小参考模板export const thunk: Middleware{ R(thunk: (dispatch: Dispatch, getState: () any) R): R } ({ dispatch, getState }) next action typeof action function ? action(dispatch, getState) : next(action)编写兼容中间件的两条规则中间件本质上是能力极强的模式可以对 action 做任何事。但链中的其他中间件可能对周围发生了什么存在隐含假设。你的中间件与外界只有两个接触点把握住这两点就能保证与常用生态中间件良好协作。规则一正确调用next调用next时它期望收到某种形式的 action。除非你要显式修改它否则原样透传收到的 action。更微妙的一点某些中间件假设中间件与dispatch的调用发生在同一 tick同步。因此你的中间件应当同步调用next。规则二正确返回dispatch的返回值除非中间件需要显式修改dispatch的返回值否则直接返回next的结果。如果确实需要修改返回值那么你的中间件必须位于中间件链中的特定位置才能如愿而且需要手动检查它与其他所有中间件的兼容性决定它们如何协同工作。陷阱异步函数会悄悄改变返回类型下面这段代码看起来只是做了点额外的事实际上已经破坏了兼容性const middleware: Middleware api next async action { const response next(action) // Do something after the action hits the reducer const afterState api.getState() if (action.type some/action) { const data await fetchData() api.dispatch(dataFetchedAction(data)) } return response }问题在于把内层函数声明为async后即便return response写的是同步值函数实际返回的也会是一个 Promise——返回类型被悄悄改变了。这会破坏像 RTK Query 这类对dispatch返回值有严格预期的中间件。正确写法把异步逻辑挪出中间件const middleware: Middleware api next action { const response next(action) // Do something after the action hits the reducer const afterState api.getState() if (action.type some/action) { void loadData(api) } return response } async function loadData(api) { const data await fetchData() api.dispatch(dataFetchedAction(data)) }要点把async/await逻辑提取到独立函数loadData中中间件本体保持同步response的返回类型不被改变。void是显式声明我故意不 await 这个 Promise对代码执行无影响但向阅读者传达了意图也避免了 eslint 的 no-floating-promises 告警。附仓库内的相关资源中间件设计史与原理从手动记录日志 → 包装 dispatch → monkeypatch → 柯里化三层函数的完整推导过程并给出 logger、crashReporter、timeoutScheduler、rafScheduler、vanillaPromise、readyStatePromise、thunk 等七个可直接运行的中间件示例。applyMiddleware API 文档参数说明、返回的 store enhancer 签名、自定义 Logger 中间件与 thunk 异步 action 的完整示例以及多 enhancer 组合时applyMiddleware应放在最前等注意事项。Store 的 dispatch 契约基础dispatch只接受普通对象 action若需派发 Promise、Observable、thunk 等必须通过中间件扩展。仓库测试 test/applyMiddleware.spec.ts覆盖中间件构造期禁止 dispatch中间件只包装一次递归 dispatch 穿过整条链与 thunk 协同透传 dispatch 额外参数等行为是验证自定义中间件行为的直接参考。【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

PTO 基础 Topk 算子实战解析:基于 TSORT32 与 TMRGSORT 的 A2/A3 排序实现

PTO 基础 Topk 算子实战解析:基于 TSORT32 与 TMRGSORT 的 A2/A3 排序实现

PTO 基础 Topk 算子实战解析:基于 TSORT32 与 TMRGSORT 的 A2/A3 排序实现 【免费下载链接】pto-isa Parallel Tile Operation (PTO) is a virtual instruction set architecture designed by Ascend CANN, focusing on tile-level operations. This repository off…

2026/9/18 23:37:19 阅读更多 →
千笔AI 的降AI率数据看懵了?TaoToken 这样改 Codex 的核对提示词

千笔AI 的降AI率数据看懵了?TaoToken 这样改 Codex 的核对提示词

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/18 23:37:19 阅读更多 →
基于YOLO的吸烟行为检测系统:从数据集到网页部署实战

基于YOLO的吸烟行为检测系统:从数据集到网页部署实战

做吸烟行为检测这个项目,起因是一位做安防集成的朋友找我说,工厂仓储区禁烟,靠保安盯着监控不现实,一个班次8小时,眼睛根本盯不住。于是我用深度学习里的目标检测思路做了一个“吸烟行为检测系统”,把模型封…

2026/9/18 23:36:18 阅读更多 →

最新新闻

YOLOv11岩石裂隙检测与三维地质建模联合优化实战

YOLOv11岩石裂隙检测与三维地质建模联合优化实战

简介:这是一份面向地质勘探、目标检测和三维建模领域从业者与研究人员的技术方案文档,聚焦YOLOv11在岩石裂隙检测与三维地质建模联合优化中的实践方法。文档从YOLO系列算法演进入手,详细剖析YOLOv11的网络结构、训练流程与检测机制&#xff0…

2026/9/19 0:17:44 阅读更多 →
从CLIP到AnomalyCLIP:零样本异常检测原理与工程复现指南

从CLIP到AnomalyCLIP:零样本异常检测原理与工程复现指南

零样本异常检测最近一年多简直是井喷式发展,核心诱因就是CLIP这类多模态基础模型的落地。CLIP原本是给图文匹配设计的,但大家突然发现,把它搬到异常检测领域,居然能实现“训练阶段完全没见过异常样本,甚至没见过对应类…

2026/9/19 0:17:44 阅读更多 →
New API 日文版指南精读:部署、环境变量与多机集群配置实战

New API 日文版指南精读:部署、环境变量与多机集群配置实战

New API 日文版指南精读:部署、环境变量与多机集群配置实战 【免费下载链接】new-api A unified AI model hub for aggregation & distribution. It supports cross-converting various LLMs into OpenAI-compatible, Claude-compatible, or Gemini-compatible …

2026/9/19 0:17:44 阅读更多 →
Java堆转储文件hprof的生成、分析与安全删除指南

Java堆转储文件hprof的生成、分析与安全删除指南

1. 什么是hprof文件?hprof文件是Java虚拟机(JVM)生成的一种堆转储(Heap Dump)文件格式。当Java应用程序出现内存泄漏或需要分析内存使用情况时,开发人员通常会生成这种文件来进行诊断。在Windows系统上&…

2026/9/19 0:17:43 阅读更多 →
LeetCode Hot 100刷题总结:从零到百题的实战路线与核心解法

LeetCode Hot 100刷题总结:从零到百题的实战路线与核心解法

HOT 100 刷完的那天,提交记录定格在 100/100。盯着绿色对勾看了几秒,我第一反应不是“终于结束了”,而是“如果当初有人早点告诉我这些,我能少走三个月的弯路”。这篇总结,就是把这些“当初没人告诉我”的东西写出来。…

2026/9/19 0:17:43 阅读更多 →
Postman批量发送请求最佳实践:从数据驱动到CI集成全攻略

Postman批量发送请求最佳实践:从数据驱动到CI集成全攻略

直接用 Postman 发单个请求,是大多数接口调试场景里的日常操作。可一旦变成需要验证 50 个用户的订单状态、给 30 个不同参数的商品详情接口做回归、或者把一批线上数据拿回来重新造数,鼠标点到手酸不说,还特别容易漏掉中间某一条。这时候真正…

2026/9/19 0:16:43 阅读更多 →

日新闻

BP神经网络时序预测:滑窗长度与多窗口平均策略

BP神经网络时序预测:滑窗长度与多窗口平均策略

简介:面向机器学习、深度学习与数据建模学习者的一份完整研究文献,聚焦BP神经网络在农业产量预测中的应用。文档以1980—2018年全国棉花产量为样本,系统讲解数据归一化处理、激活函数原理、多层神经网络结构搭建及训练流程,展示敏…

2026/9/19 0:00:30 阅读更多 →
Transformer训练实时监控实战:基于MindSpore的损失曲线可视化方案

Transformer训练实时监控实战:基于MindSpore的损失曲线可视化方案

上个月调一个Deformable DETR模型,在单卡上要跑将近两天。第二天早上我下意识打开终端翻日志,发现loss从凌晨两点就开始往上爬,一路从0.8涨到1.35,整整六个小时没人发现。那六个小时的训练不仅白跑,还霸占着卡——等于…

2026/9/19 0:00:30 阅读更多 →
OpenCloud 中的 Go 类型安全转换库 spf13/cast:从零值回退到泛型 API 的完整实战指南

OpenCloud 中的 Go 类型安全转换库 spf13/cast:从零值回退到泛型 API 的完整实战指南

OpenCloud 中的 Go 类型安全转换库 spf13/cast:从零值回退到泛型 API 的完整实战指南 【免费下载链接】opencloud 🌤️ OpenCloud is the open source platform for file management, sharing and collaboration. Simple and sovereign. 项目地址: htt…

2026/9/19 0:00:30 阅读更多 →

周新闻

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and ag…

2026/9/16 19:03:19 阅读更多 →
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: https://gitcode.com/GitH…

2026/9/17 7:57:36 阅读更多 →
Flutter应用改名全指南:从Android到iOS的配置与工具实践

Flutter应用改名全指南:从Android到iOS的配置与工具实践

刚接一个外包项目时,甲方要求把工程里临时用的应用名改成正式产品名。我本来觉得“改名”这种小事,打开配置文件改一行不就完了?结果真动手才发现,Flutter项目里“应用名称”根本不是一处配置,而是一整套散落在 Androi…

2026/9/17 10:19:14 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/16 22:31:27 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/15 21:39:18 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/16 22:32:59 阅读更多 →