前端【免费下载链接】redux-observableRxJS middleware for action side effects in Redux using Epics项目地址https://gitcode.com/gh_mirrors/re/redux-observable点击查看免费下载本篇指南聚焦 redux-observable 中真正的核心对象——EpicMiddleware 实例及其唯一公开方法run(rootEpic)。在 Redux 应用中createEpicMiddleware()创建的中间件实例负责把 Epic 接入 store而run()则是让 Epic 真正开始监听 action 的点火开关。读完本文你将掌握 EpicMiddleware 的正确创建与挂载流程、run()的调用时机与多次调用语义、源码级别的内部工作机制以及如何利用它实现代码分割下的动态加载与热模块替换HMR。一、EpicMiddleware 是什么官方 API 文档对 EpicMiddleware 的定义非常精炼它是 redux-observable 中间件的一个实例an instance of the redux-observable middleware。它不是一个独立导出的类而是通过调用createEpicMiddleware函数创建出来的具体对象创建之后你就拥有了一个能够被 ReduxapplyMiddleware/configureStore使用的标准中间件。从源码的类型定义可以看到它的完整形状src/createEpicMiddleware.ts#L12-L20export interface EpicMiddleware Input unknown, Output extends Input Input, State void, Dependencies any extends Middleware{}, State { run(rootEpic: EpicInput, Output, State, Dependencies): void; }几点关键信息它继承自 Redux 的Middleware接口所以可以直接作为中间件传给applyMiddleware(...)或 Redux Toolkit 的middleware配置除了标准中间件行为外它额外暴露了唯一的一个方法run(rootEpic)这也是本文的核心主题类型参数Input输入 action、OutputEpic 输出的 action约束为Input的子类型、Statestore 状态类型、Dependencies注入的依赖类型用于提供完整的 TypeScript 类型安全。也就是说EpicMiddleware 实例同时扮演两个角色一是 Redux 的中间件拦截 action 流、维护action$与state$二是 Epic 的宿主通过run()接收并启动 Epic。二、创建实例createEpicMiddleware要得到 EpicMiddleware 实例必须调用createEpicMiddleware。它接受一个可选的配置对象import { createEpicMiddleware } from redux-observable; import { rootEpic } from ./modules/rootEpic; const epicMiddleware createEpicMiddleware(); // 无配置最常用配置项说明源码中的Options接口见 src/createEpicMiddleware.ts#L8-L10配置项类型默认值说明dependenciesany无若提供会被作为第 3 个参数注入到所有 Epic 中dependencies最常见的用途是依赖注入例如把rxjs/ajax的getJSON注入给 Epic从而让测试时可以轻松替换网络层详见后文依赖注入一节const epicMiddleware createEpicMiddleware({ dependencies: { getJSON: ajax.getJSON }, });一个重要的历史性警告在 v1.0.0 之前API 允许把 rootEpic 直接传给createEpicMiddleware(rootEpic)。这个旧写法如今已被移除——源码在非生产环境下检测到传入函数时会直接抛出TypeErrorsrc/createEpicMiddleware.ts#L41-L45if (process.env.NODE_ENV ! production typeof options function) { throw new TypeError( Providing your root Epic to createEpicMiddleware(rootEpic) is no longer supported, instead use epicMiddleware.run(rootEpic) ); }对应的测试用例should throw an error if you provide a function to createEpicMiddleware (used to be rootEpic)也验证了这一行为test/createEpicMiddleware-spec.ts#L54-L62。正确的姿势永远是先createEpicMiddleware()后epicMiddleware.run(rootEpic)。三、核心 APIrun(rootEpic)run(rootEpic)是 EpicMiddleware 实例唯一公开的方法官方文档对其描述为Run the middleware with the provided epic.用给定的 epic 运行该中间件3.1 参数参数类型必填说明rootEpicEpic是中间件要使用的 EpicEpic的类型签名定义在 src/epic.tsexport declare interface Epic Input unknown, Output extends Input Input, State void, Dependencies any { ( action$: ObservableInput, state$: StateObservableState, dependencies: Dependencies ): ObservableOutput; }也就是说rootEpic是一个接收三个参数action$、state$、dependencies并返回ObservableOutput的函数。传入run()的 epic 会拿到action$所有经过 store 的 action 组成的 Observablestate$当前状态的 ObservableStateObservable实例可直接访问state$.valuedependencies由createEpicMiddleware({ dependencies })注入的依赖。3.2 返回值run()返回void——它只是把 epic 提交给中间件内部不返回任何值也不需要你手动订阅任何东西。3.3 调用时机必须在 store 创建之后run()必须在该中间件实例已被 Redux 接入store 创建完成之后调用。源码对此有防御性检查src/createEpicMiddleware.ts#L111-L118epicMiddleware.run (rootEpic) { if (process.env.NODE_ENV ! production !store) { warn( epicMiddleware.run(rootEpic) called before the middleware has been setup by redux. Provide the epicMiddleware instance to createStore() first. ); } epic$.next(rootEpic); };如果先run()后建 store非生产环境下会输出警告提示。正确的顺序是createEpicMiddleware()创建实例把实例传给configureStore/createStore最后调用epicMiddleware.run(rootEpic)。3.4 多次调用的语义合并而非替换文档特别强调You might need to call this multiple times if your app implements code splitting and you want to load some of the epics dynamically or youre using hot reloading.如果你的应用实现了代码分割想动态加载部分 epic或使用了热重载你可能需要多次调用它。这一点在 MIGRATION.md 中有更精确的说明Subsequent calls ofepicMiddleware.run(epic)do not replace the previous ones, they are merged together.后续对run(epic)的调用不会替换之前的 epic而是合并在一起。这一语义源于源码中epic$是一个Subjectsrc/createEpicMiddleware.ts#L47每次run()只是向这个 Subject 推入一个新的 epicepic$.next(rootEpic)所有已提交的 epic 会被mergeMap合并订阅彼此并行运行。这为运行期间动态加入新 epic提供了天然支持。四、为什么需要 run1.0.0 的 API 变迁理解run()的价值最好先了解它的来历。MIGRATION.md 记录了这一重要变更In 1.0.0 you no longer provide your root Epic tocreateEpicMiddleware. Instead, you callepicMiddleware.run(rootEpic)on the instance of the middlewareafteryou have created your store with it.1.0.0 起你不再把 root Epic 交给createEpicMiddleware而是在用它创建 store之后调用epicMiddleware.run(rootEpic)。变更的根本原因有两个Redux v4 的限制在 Redux v4 中中间件装配阶段不允许再 dispatch action而旧 API 下 Epic 可能在中间件 setup 期间就派发 action从而触发问题支持运行时添加 Epic新 API 使先建 store、后挂 Epic、随时再加 Epic成为可能为代码分割和 HMR 铺平了道路。同时旧版replaceEpic也被移除官方给出的替代方案是自行派发一个ENDaction 让根 Epic 用takeUntil终止旧流再调用run(nextEpic)挂载新 Epic见 MIGRATION.md 的Replace Epic部分。五、源码视角run 的内部工作机制run()看似简单背后是 redux-observable 的全部核心管线。我们顺着 src/createEpicMiddleware.ts 逐段拆解。5.1 独立队列调度器中间件创建时会基于 RxJS 的queueScheduler构造一个独立的队列调度器实例src/createEpicMiddleware.ts#L30-L39const QueueScheduler: any queueScheduler.constructor; const uniqueQueueScheduler: typeof queueScheduler new QueueScheduler( (queueScheduler as any).schedulerActionCtor );注释解释了原因redux-observable 不希望自己的内部队列机制与 RxJS 其他代码共用同一个队列。这个uniqueQueueScheduler后续会同时用于action$、state$以及 epic 输出的调度保证 action 的派发、状态的更新与 epic 的处理严格串行、顺序稳定。5.2 中间件核心action$ 与 state$ 的构建当中间件被 Redux 装配时src/createEpicMiddleware.ts#L50-L109会创建两个 Subject 并把它们包装成可观察流action$由actionSubject$派生经observeOn(uniqueQueueScheduler)调度state$由stateSubject$派生并包装成StateObservable实例初始值为store.getState()。StateObservable是一个带value属性的 Observablesrc/StateObservable.ts它只在状态实际发生变化时才通知订阅者本质是distinctUntilChanged的简化实现因此 Epic 内可以放心读取state$.value。测试用例should allow accessing state$.value on epic startup验证了在 Epic 启动时即可同步读取当前状态test/createEpicMiddleware-spec.ts#L152-L177。5.3 关键执行顺序先 reducer后 epic中间件返回的 dispatch 包装函数揭示了 redux-observable 最重要的执行顺序保证src/createEpicMiddleware.ts#L94-L108return (next) { return (action) { // Downstream middleware gets the action first, // which includes their reducers, so state is // updated before epics receive the action const result next(action); // Its important to update the state$ before we emit // the action because otherwise it would be stale stateSubject$.next(store.getState()); actionSubject$.next(action as Input); return result; }; };流程是action 先经下游中间件与 reducer 完成状态更新 → 再把新状态推入state$→ 最后把 action 推入action$。这样 Epic 接收到的state$永远是与当前 action 对应的最新状态。测试用例should update state$ after an action goes through reducers but before epics专门验证了这一时序test/createEpicMiddleware-spec.ts#L87-L150。5.4 run 的完整链路综合起来run(rootEpic)触发的是这样一条管道src/createEpicMiddleware.ts#L70-L92const result$ epic$.pipe( map((epic) { const output$ epic(action$, state$, options.dependencies!); if (!output$) { throw new TypeError( Your root Epic ${epic.name || anonymous} does not return a stream. Double check youre not missing a return statement! ); } return output$; }), mergeMap((output$) from(output$).pipe( subscribeOn(uniqueQueueScheduler), observeOn(uniqueQueueScheduler) ) ) ); result$.subscribe(store.dispatch);每次run(epic)把 epic 推入epic$Subjectmap调用该 epic传入action$、state$、dependencies并校验返回值——若 Epic 没有返回流比如漏写return会抛出带 Epic 名字的TypeError帮助排查mergeMap把每个 epic 的输出流扁平化合并并用独立调度器确保串行有序最终result$.subscribe(store.dispatch)——epic 派发的每个 action 都会重新进入 store形成完整的闭环。5.5 store 关联的唯一性警告如果一个中间件实例被同时用于多个 store源码会给出警告src/createEpicMiddleware.ts#L53-L58if (process.env.NODE_ENV ! production store) { warn( this middleware is already associated with a store. createEpicMiddleware should be called for every store. ); }结论很明确每个 store 都应该拥有自己独立的createEpicMiddleware()实例不要跨 store 复用。测试用例should warn about reusing the epicMiddleware验证了该行为test/createEpicMiddleware-spec.ts#L64-L85。六、完整集成从 rootEpic 到 store 与 run结合docs/basics/SettingUpTheMiddleware.md与docs/api/createEpicMiddleware.md一个完整的接入流程如下。6.1 组装 rootEpicredux-observable 要求提供一个单一 root Epic通常用combineEpics把多个 Epic 合并注意合并顺序会影响 Epic 的执行与接收 action 的顺序// app/rootEpic.js import { combineEpics } from redux-observable; import { pingEpic } from features/ping/pingEpic; import { fetchUserEpic } from features/users/usersEpic; export const rootEpic combineEpics(pingEpic, fetchUserEpic);6.2 配置 store 并运行// app/store.js import { configureStore } from reduxjs/toolkit; import { createEpicMiddleware } from redux-observable; import { rootEpic } from ./rootEpic; import ping from features/ping/pingSlice; import users from features/users/usersSlice; const epicMiddleware createEpicMiddleware(); export const store configureStore({ reducer: { ping, users, }, middleware: (getDefaultMiddleware) getDefaultMiddleware().concat(epicMiddleware), }); epicMiddleware.run(rootEpic);6.3 全局错误处理未捕获的错误会冒泡到根 Epic 并导致整个流终止——一旦发生中间件里已注册的 Epic 将不再运行。官方推荐在根 Epic 上加一个全局错误处理器捕获错误并重新订阅源流docs/basics/SettingUpTheMiddleware.mdconst rootEpic (action$, store$, dependencies) combineEpics(...epics)(action$, store$, dependencies).pipe( catchError((error, source) { console.error(error); return source; // 重新订阅让 Epic 继续运行 }), );注意事项catchError内可以接入任意日志上报工具但重启根 Epic 可能有副作用——如果你的应用使用了有状态 Epic重启会丢失这些内部状态。七、利用 run 实现动态加载与代码分割因为run()可以多次调用且合并而非替换动态地向运行中的应用添加新 Epic 非常安全。官方配方docs/recipes/AddingNewEpicsAsynchronously.md用BehaviorSubjectmergeMap实现了这一点import { BehaviorSubject } from rxjs; import { combineEpics } from redux-observable; const epic$ new BehaviorSubject(combineEpics(epic1, epic2)); const rootEpic (action$, state$) epic$.pipe( mergeMap(epic epic(action$, state$)), ); // 应用运行一段时间后……异步加载并加入新 Epic旧 Epic 保持运行状态 epic$.next(asyncEpic1); // 再晚一些…… epic$.next(asyncEpic2);要点mergeMap会让新旧 Epic并行共存旧 Epic 继续以原状态运行因此这只适合添加不适合替换正在运行的 Epic替换有状态 Epic 可能产生奇怪 bug详见下一节与docs/recipes/HotModuleReplacement.md。八、利用 run 实现热模块替换HMR热重载场景下需要替换正在运行的 Epic。官方配方同样以run()为入口docs/recipes/HotModuleReplacement.md。8.1 方案一switchMap 替换switchMap的特性是新值到来时退订旧流、订阅新流正好符合替换语义import { rootEpic } from ./where-ever-they-are; import { BehaviorSubject } from rxjs; import { switchMap } from rxjs/operators; const epicMiddleware createEpicMiddleware(); const store configureStore({ reducer: { // ... reducers ... }, middleware: (getDefaultMiddleware) getDefaultMiddleware().concat(epicMiddleware), }); const epic$ new BehaviorSubject(rootEpic); const hotReloadingEpic (...args) epic$.pipe( switchMap(epic epic(...args)), ); epicMiddleware.run(hotReloadingEpic); if (module.hot) { module.hot.accept(./where-ever-they-are, () { const nextRootEpic require(./where-ever-they-are).rootEpic; epic$.next(nextRootEpic); // 新 epic 到来switchMap 退订旧的、订阅新的 }); }8.2 方案二EPIC_END 信号 takeUntil更可控的方式是派发一个EPIC_ENDaction 主动终止旧 Epic再加载新的const epic$ new BehaviorSubject(rootEpic); const hotReloadingEpic (action$, ...rest) epic$.pipe( mergeMap(epic epic(action$, ...rest).pipe( takeUntil(action$.pipe(ofType(EPIC_END))), ), ), ); epicMiddleware.run(hotReloadingEpic); if (module.hot) { module.hot.accept(./where-ever-they-are, () { const nextRootEpic require(./where-ever-they-are).rootEpic; store.dispatch({ type: EPIC_END }); // 先杀掉所有在跑的 epic epic$.next(nextRootEpic); // 再挂载新的 }); }这与 MIGRATION.md 中关于replaceEpic的替代方案完全一致。注意 HMR 仅在本地开发时使用且替换有状态 Epic 可能造成状态丢失——这是副作用中间件固有的问题并非 redux-observable 独有。九、依赖注入与可测试性run()接收的 rootEpic 会拿到createEpicMiddleware注入的dependencies第三个参数。官方配方docs/recipes/InjectingDependenciesIntoEpics.md展示了这一模式如何让测试变得简单// 创建中间件时注入依赖 const epicMiddleware createEpicMiddleware({ dependencies: { getJSON: ajax.getJSON }, }); epicMiddleware.run(rootEpic); // Epic 中直接使用注入的依赖而非自行 import const fetchUserEpic (action$, state$, { getJSON }) action$.pipe( ofType(FETCH_USER), mergeMap(({ payload }) getJSON(/api/users/${payload}).pipe( map(response ({ type: FETCH_USER_FULFILLED, payload: response, })), )), );测试时直接调用 Epic 并传入 mock 依赖即可无需触碰中间件本身const mockResponse { name: Bilbo Baggins }; const action$ of({ type: FETCH_USERS_REQUESTED }); const state$ null; // 该 epic 不需要 state const dependencies { getJSON: (url) of(mockResponse), }; const result$ fetchUserEpic(action$, state$, dependencies).pipe( toArray(), // 缓冲输出直到 Epic 自然完成 ); result$.subscribe((actions) { assertDeepEqual(actions, [ { type: FETCH_USER_FULFILLED, payload: mockResponse }, ]); });十、常见陷阱与排错速查现象原因与对策createEpicMiddleware(rootEpic)抛TypeError使用了 v1.0.0 之前的旧 API改为epicMiddleware.run(rootEpic)src/createEpicMiddleware.ts#L41-L45控制台警告 already associated with a store同一个中间件实例被用于多个 store应为每个 store 单独createEpicMiddleware()src/createEpicMiddleware.ts#L53-L58警告 run(rootEpic) called before the middleware has been setup在 store 创建前调用了run()调整顺序为先建 store 后run()src/createEpicMiddleware.ts#L112-L116TypeError: Your root Epic ... does not return a streamEpic 函数漏写return或返回了非 Observable 值src/createEpicMiddleware.ts#L74-L80某次 Epic 抛错后所有 Epic 停止响应未捕获错误终止了整个流请用根 Epic 的catchError做全局错误处理并return source重新订阅想替换Epic 却用了多次run()run()多次调用是合并语义替换请用BehaviorSubjectswitchMap或EPIC_ENDtakeUntil方案总结EpicMiddleware 实例是 redux-observable 与 Redux store 之间的桥梁而run(rootEpic)则是这座桥的通行开关它在 store 创建完成后被调用把 rootEpic 提交给内部的epic$Subject随后 Epic 的输出流经mergeMap合并后直接订阅到store.dispatch形成action 进、action 出的闭环。由于多次run()是合并而非替换它天然支持代码分割下的异步加 Epic 与 HMR 热替换配合createEpicMiddleware的dependencies注入还能让 Epic 保持高度可测试性。掌握好先建 store、后run()、善用多次调用这三个要点就能在真实项目中安全、灵活地驾驭 redux-observable 的副作用管理。延伸阅读仓库内createEpicMiddleware 完整 APIcombineEpics合并多个 Epic中间件接入指南Epic 基础概念异步添加新 Epic热模块替换配方依赖注入与测试配方迁移指南1.0.0 API 变更中间件核心实现源码Epic 类型定义StateObservable 实现中间件行为测试用例赞分享前端【免费下载链接】redux-observableRxJS middleware for action side effects in Redux using Epics项目地址https://gitcode.com/gh_mirrors/re/redux-observable点击查看免费下载相关推荐Redux Thunk与React Native启动屏幕状态预加载Redux Thunk与React Native启动屏幕状态预加载 你是否在React Native应用中遇到过这样的问题用户打开应用看到启动屏幕后主界前端FastAPI CLI 完全指南用 fastapi dev 与 fastapi run 启动、调试和部署 FastAPI 应用FastAPI CLI 完全指南用 fastapi dev 与 fastapi run 启动、调试和部署 FastAPI 应用 FastAPI CLI 是随后端Web框架API设计Redux Thunk终极指南如何实现React Native应用启动优化与状态预加载Redux Thunk终极指南如何实现React Native应用启动优化与状态预加载 Redux Thunk是Redux官方推荐的中间件专门用于处理异步操前端上一篇YouTube.js 中的 ThumbnailOverlayBadgeView解析缩略图角标叠加层的节点类下一篇GoReleaser v2.16 发布解读不可变发布、Node.js SEA 构建器与 dockers_v2 转正创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
好用还专业!2026年必不可少的专业AI论文平台 2026年AI论文写作工具已从“基础生成”升级为融合智能写作、学术合规与高效协作的全流程平台,核心评价维度涵盖文献真实性、格式合规性、长文本逻辑、查重降重、AIGC合规及多语言支持。本次测评覆盖6款主流工具,涵盖中英文论文、全流程与专项功能、免费与… · 2026/9/27 10:08:28
2026最新怎样创建个人网站:告别无人访问,从设计规范做起 2026最新怎样创建个人网站:告别无人访问,从设计规范做起 网站做好了没人访问,这大概是90%个人站长最绝望的时刻。你熬夜敲代码、调像素,上线后打开百度统计,PV个位数,UV全是自己。很多老板或开发者以为“没人看”是因为流量不够,其实不然。… · 2026/9/27 16:27:12
石家庄有没有销售做被用的网站?新手入门避坑指南 石家庄有没有销售做被用的网站?新手入门避坑指南 域名服务器搞不懂?别慌。 很多石家庄的朋友问我, 石家庄有没有销售做被用的网站 这种奇葩需求,其实大家想问的是:… · 2026/9/27 16:27:12
用 d3 实现 Collapsible Indented Tree:从数据到可折叠缩进树的完整配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 16:27:06
告别模板丑站:济宁市松岳建设机械有限公司网站SEO实战与免费工具 告别模板丑站:济宁市松岳建设机械有限公司网站SEO实战与免费工具 还在为模板网站太丑、转化率低而头疼?很多老板觉得做个站就像买个衣服,挑个好看的就行,结果上线后既没客户又没排名,钱花了却听个响。别急,今天咱们不聊虚的,直接拆解【济宁市松岳建… · 2026/9/27 16:27:06
浙大开源Polaris实战:用TaoToken统一Key跑通AI科研智能体六阶段流水线 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 16:26:59
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01