easy-peasy Reducer TypeScript API:用 Reducer<State, Action> 为 Redux 风格 Reducer 添加完整类型
前端【免费下载链接】easy-peasyVegetarian friendly state for React项目地址https://gitcode.com/gh_mirrors/ea/easy-peasy点击查看免费下载本篇围绕 easy-peasy 的 TypeScript API 中的Reducer类型展开讲解如何在模型声明中为“标准 Redux 风格的 reducer 函数”赋予完整的State与Action泛型约束同时结合仓库源码剖析reducer()在 store 创建、dispatch 分发、immer 集成中的完整运行时链路帮助你在引入redux-first-history这类外部 Redux 库或从旧版 Redux 代码渐进迁移时同时获得类型安全与可验证的运行时行为。Reducer 的定位把 Redux 惯用法接入 easy-peasy 模型easy-peasy 的默认状态更新方式是action()/thunk()但生态中仍有许多基于“标准 reducer”构建的 Redux 库。例如redux-first-history用于路由历史状态管理要求你把它提供的 reducer 挂到 store 上。reducer()就是为此设计的 API它声明一段状态由一个经典(state, action) state函数来计算语义与 Redux 完全一致。这一设计目标在官方文档 api/reducer.md 中明确写道“This API specifically exists to help with integrations against existing Redux based libraries, and to help with gradual migration from legacy Redux code.”该 API 专门用于帮助与既有 Redux 库集成并辅助从旧 Redux 代码渐进迁移。类型层面的对应物就是本篇的核心ReducerState, Action。它在 TypeScript 模型中承担“这段状态由 reducer 管理”的类型标记让StateModel、ActionsModel等工具类型能够正确推导。运行时 APIreducer() 的完整参数说明在讲类型之前先完整继承 api/reducer.md 中reducer()函数的运行时签名与参数这是理解类型的前提reducer((state 1, action) { switch (action.type) { case INCREMENT: return state 1; default: return state; } });参数一reducer 函数必填接收两个参数state必填当前 reducer 所挂载属性上的状态值。注意是该属性的状态而不是整个 store 的状态action必填被 dispatch 的 action 对象通常为以下形状typestring必填动作名称payloadany可选action 携带的任意负载。参数二config 配置对象可选immerBoolean是否使用immer更新状态默认true。官方文档特别提示当你管理的大/深数据结构在 reducer 中做大量更新时immer 的 Proxy 方案会引入额外开销只有在确实观察到性能退化时才建议通过{ immer: false }关闭这条“逃生通道”。关于immer: false的底层实现可以查看 src/lib.js 中的createSimpleProducereturn function simpleProduce(path, state, fn, config) { if ((config immer in config) ? config?.immer false : disableImmer) { const current get(path, state); const next fn(current); if (current ! next) { return newify(path, state, next); // 沿 path 做不可变拷贝绕过 immer } return state; } // ... 否则走 immer produce 流程 };优先级逻辑是reducer自身的config.immer若显式存在则以它为准否则回退到 store 级的disableImmer配置且当 reducer 返回值引用不变current next时直接返回原状态不产生新的对象。TypeScript APIReducerState, Action 类型签名回到本篇主题文档 website/docs/docs/typescript-api/reducer.md其定义的 API 为Reducer State any, Action extends ReduxAction ReduxAction 泛型参数默认值含义Stateany将由该 reducer 管理的那段状态的类型ActionReduxActionreducer 可能接收到的 action 的类型约束在 index.d.ts 中Reducer的实际定义为export type ReducerState any, Action extends ReduxAction UnknownAction { type: reducer; result: State; };两个关键点ReduxAction在 index.d.ts#L14 中被定义为export type ReduxAction UnknownAction;即默认放宽为 Redux 的UnknownAction因此不显式传第二个泛型时任意type字段都是合法的 action与“经典 Redux action”兼容result: State是类型系统对接的“锚点”。在StateMapper的类型推导中index.d.ts#L253-L263凡是Reducerany, any的字段都会被解包为StateModel[P][result]type StateMapperStateModel extends object { [P in keyof StateModel]: // ... : StateModel[P] extends Reducerany, any ? StateModel[P][result] // ... };也就是说模型里声明todos: Reducerstring[]之后StateModel会把todos推导为string[]—— 这正是 TypeScript API 文档与运行时 API 文档能够互相印证的地方。对应的reducer()工厂函数类型声明见 index.d.ts#L777-L780export function reducerState( reducer: ReduxReducerState, config?: Config, ): ReducerState;其中Config接口index.d.ts#L599-L601即前面运行时配置的类型化版本{ immer?: boolean }。完整示例在模型中使用 Reducerstring[]官方 TypeScript 文档给出的完整示例如下展示如何为一段string[]状态声明 reducerimport { Reducer, reducer } from easy-peasy; interface StoreModel { todos: Reducerstring[]; } const storeModel: StoreModel { todos: reducer((state [], action) { switch (action.type) { case ADD_TODO: return [...state, action.payload]; default: return state; } }) }逐行解读interface StoreModel中用Reducerstring[]而非string[]声明todos向类型系统表明“这段状态不由普通初始值决定而由 reducer 计算”reducer((state [], action) ...)中state []是 reducer 未收到初始状态时的默认值easy-peasy 会用模型上该 key 的初始 state 作为 reducer 的第一个参数基准这里用默认参数做兜底case ADD_TODO分支中action.payload是 dispatch 时随 action 携带的负载由于todos: Reducerstring[]StateStoreModel[todos]会被推导为string[]useStoreState((state) state.todos)即获得完整的数组类型。同样的模式在仓库的 TypeScript 类型测试中被反复使用。例如 tests/typescript/state.ts 与 tests/typescript/actions.ts 都在模型中声明了reducerImp: Reducernumber并分别断言StateModel与ActionsModel的推导结果——Actions会把 reducer 字段过滤掉它不是可调用方法而State会把Reducernumber解包为number。运行时实现链路从 reducer() 到 dispatch结合源码reducer()从声明到执行的完整链路如下每一步都有仓库文件可查证。1. 工厂函数打上 reducerSymbol 标记src/helpers.js#L82-L86export const reducer (fn, config) ({ [reducerSymbol]: true, fn, config, });reducer()返回的并不是可执行对象而是一个携带reducerSymbol标记、原始函数与配置的描述对象等待 store 创建阶段被“萃取”。2. 模型萃取登记到 _cR 自定义 reducer 列表createStore 时src/extract-data-from-model.js#L155-L161 递归遍历模型遇到带reducerSymbol的字段时执行} else if (value[reducerSymbol]) { _cR.push({ key, parentPath, reducer: value.fn, config: value.config, }); }parentPath记录了该 reducer 在状态树中的路径不含 key 本身config原样透传——这就解释了为什么 reducer 可以嵌套在任意深度的子模型里官方测试tests/reducer.test.js的nested用例验证了stuff.counter这样的嵌套场景。3. 根 reducer 组装自定义 reducer 在每个 action 后依次运行src/create-reducer.js#L24-L35 中的reducerForCustomReducers是关键const reducerForCustomReducers (state, action) _cR.reduce( (acc, { parentPath, key, reducer, config }) simpleProduce(parentPath, acc, (draft) { draft[key] reducer( isDraft(draft[key]) ? original(draft[key]) : draft[key], action, ); return draft; }, config), state, );三个细节值得注意每个 action 都会流经所有注册的自定义 reducer包括 easy-peasy 自己的action.*内部 actionreducer 需要自行判断action.type是否与自己相关不相关则返回原 stateisDraft(draft[key]) ? original(draft[key]) : draft[key]保证传入用户 reducer 的 state 永远是“真实值”而非 immer draft避免外部 reducer 操作 draft 造成副作用整个_cR的执行发生在 easy-peasy 自有 action reducer 之后src/create-reducer.js#L37-L50 的rootReducer且仅当存在自定义 reducer_cR.length 0时才进入该分支零额外开销。组装入口在 src/create-store.js#L59-L60createReducer(disableImmer, data._aRD, data._cR, data._cP)生成的根 reducer 会再经过 store 配置中的reducerEnhancer类型见 index.d.ts#L358包装后交给 Redux store——这正是与redux-persist等“包裹根 reducer”的生态库的对接点仓库中 tests/typescript/store.ts 展示了用reducerEnhancer套一层persistReducer的完整类型推导。4. 不变性与引用相等immer 分支使用了一个全局共享的Immer({ autoFreeze: false })实例src/lib.js#L97-L103注释说明关闭 autoFreeze 是因为 easy-peasy 需要“可变与不可变混合”来更新 computed 属性。当 reducer 返回原引用时no-op整个 store 状态引用保持不变。这正是 tests/reducer.test.js#L59-L78 的no-op用例所验证的const initial store.getState(); store.getActions().doNothing(); expect(store.getState()).toBe(initial); // 引用相等对依赖比较做 memo/shouldUpdate 判断的组件而言这一行为意味着 reducer 中“返回未变化的 state”可以直接避免不必要的重渲染。结合 React 的完整用法继承 api/reducer.md 的官方示例展示reducer()与 React hooks 的配合import { createStore, reducer, useStoreState, useStoreDispatch, } from easy-peasy; const store createStore({ counter: reducer((state 1, action) { switch (action.type) { case INCREMENT: return state 1; default: return state; } }), }); function Counter() { const count useStoreState((state) state.counter); const dispatch useStoreDispatch(); return ( button onClick{() dispatch({ type: INCREMENT })}{count}/button ); }注意这里使用的是useStoreDispatch()直接 dispatch 原始{ type }action而不是useStoreActions()——reducer 管理的状态段天然对应 Redux 的 dispatch 语义。仓库中的 tests/reducer.test.js#L3-L33 的非 React 版本验证了等价行为dispatch{ type: INCREMENT }后store.getState().counter从 1 变为 2且同一模型中普通的action()状态foo.bar不受影响。常见使用场景小结接入依赖“标准 reducer”的 Redux 生态库把库提供的 reducer 用reducer(...)包进 easy-peasy 模型模型中声明ReducerT类型即可获得State推导index.d.ts的 JSDoc 示例index.d.ts#L746-L752演示的正是router: ReducerReactRouterState这类路由场景从 Redux 渐进迁移旧代码的 slice 可以整体作为reducer(...)挂入新模型按action.type分支逐步改写为action()/thunk()迁移期间两套范式可在同一 store 中共存性能敏感的大数据段确认存在性能退化后通过第二个参数reducer(fn, { immer: false })走newify的浅拷贝路径绕开 Proxy 开销见 src/lib.js#L87-L96需要包裹根 reducer 的中间件类库使用 store 配置的reducerEnhancer在 easy-peasy 根 reducer 外层套壳自定义 reducer 在其内层照常工作。快速参考项目说明依据ReducerState, Action模型中声明 reducer 管理状态段的类型State默认anyAction默认ReduxAction即UnknownActionindex.d.ts#L754-L757reducer(fn, config?)工厂函数返回{ [reducerSymbol], fn, config }描述对象src/helpers.js#L82-L86config.immer默认truefalse时走非 immer 的newify路径src/lib.js#L87-L96嵌套支持通过parentPath注册任意深度均可嵌套src/extract-data-from-model.js#L155-L161、tests/reducer.test.js#L35-L57no-op 行为reducer 返回原引用时 store 状态引用不变tests/reducer.test.js#L59-L78类型解包StateModel将ReducerT解包为Tindex.d.ts#L253-L263如需进一步了解运行时侧的完整 API 与参数语义可对照阅读 api/reducer.md本篇的主题文档原文见 typescript-api/reducer.md。赞分享前端【免费下载链接】easy-peasyVegetarian friendly state for React项目地址https://gitcode.com/gh_mirrors/ea/easy-peasy点击查看免费下载相关推荐easy-peasy reducer API 完全指南在 React 状态模型中接入标准 Redux Reducereasy peasy reducer API 完全指南在 React 状态模型中接入标准 Redux Reducer 导读 reducer 是 easy pe前端react-redux-typescript-guide Reducer测试使用Jest测试Redux Reducerreact redux typescript guide Reducer测试使用Jest测试Redux Reducer 在React Redux应用开发中R前端教程Redux类型安全架构Action、Reducer与Store的完全类型化Redux类型安全架构Action、Reducer与Store的完全类型化 本文详细介绍了如何构建完全类型安全的Redux架构涵盖从Action Creat前端教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

EFK日志监控系统从零搭建:Elasticsearch+Filebeat+Kibana实战指南

EFK日志监控系统从零搭建:Elasticsearch+Filebeat+Kibana实战指南

直接说结论:如果你团队里缺一套能随时查线上日志、能统计错误趋势、能按关键字秒级定位问题的系统,EFK三件套是目前最稳妥的选择。Elasticsearch负责存储和检索,Filebeat轻量采集日志,Kibana负责可视化。三者合起来,就…

2026/10/14 0:15:46 阅读更多 →
二叉树指定结点层次计算:递归与迭代实现及工程实践

二叉树指定结点层次计算:递归与迭代实现及工程实践

1. 从一道经典题说起:为什么"求结点层次"值得单独拎出来讲但凡刷过二叉树相关题目的人,大概率都见过这道题:给一棵二叉树,再给一个目标结点,问这个结点在第几层。很多人第一反应是"这不就是遍历的时候顺…

2026/10/14 0:15:46 阅读更多 →
Lorenz吸引子实战指南:从混沌方程到三维相图可视化

Lorenz吸引子实战指南:从混沌方程到三维相图可视化

1. 为什么一张“蝴蝶翅膀”能搅动整个非线性科学界?你可能在科普视频里见过它:两条螺旋状轨迹,像一对缓缓张开的蝴蝶翅膀,在三维空间里永不停歇地缠绕、分离、再靠近——它不是艺术渲染,而是Lorenz系统在特定参数下自动…

2026/10/14 0:14:45 阅读更多 →

最新新闻

Dart List实战指南:从增删改查到Flutter状态管理踩坑总结

Dart List实战指南:从增删改查到Flutter状态管理踩坑总结

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

2026/10/14 1:06:04 阅读更多 →
HMM隐马尔可夫模型Python实现:前向算法、Viterbi与Baum-Welch实战

HMM隐马尔可夫模型Python实现:前向算法、Viterbi与Baum-Welch实战

HMM(Hidden Markov Model,隐马尔可夫模型)这个名词,做 NLP、语音识别、行为序列分析的开发者应该都不陌生。我记得刚入门的时候,教材翻了好几遍,图模型和公式推导都能看懂,但一到写代码就卡住—…

2026/10/14 1:06:04 阅读更多 →
Anaconda 环境管理实战:conda 命令、虚拟环境与 Jupyter Notebook 使用指南

Anaconda 环境管理实战:conda 命令、虚拟环境与 Jupyter Notebook 使用指南

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

2026/10/14 1:06:04 阅读更多 →
一维随机变量:从离散到连续,概率论核心概念解析

一维随机变量:从离散到连续,概率论核心概念解析

1. 从“抛硬币”到“连续取值”:一维随机变量到底在解决什么问题很多人学概率论,学到“随机变量”这一章会突然卡住。前面古典概型还能靠数数、排列组合硬算,到了随机变量,突然冒出来“分布函数”“概率密度”“连续型”这些词&am…

2026/10/14 1:06:04 阅读更多 →
B树在文献管理中的应用:从选型到实现的完整指南

B树在文献管理中的应用:从选型到实现的完整指南

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

2026/10/14 1:06:04 阅读更多 →
Tableau标靶图实战:四层结构与LOD归一化构建法

Tableau标靶图实战:四层结构与LOD归一化构建法

1. 项目概述:标靶图不是“画个圆圈加个点”那么简单标靶图(Bullet Chart)在Tableau里常被误认为是“带靶心的条形图”,但真正用好它,得先扔掉这个认知。我带过不少刚接触Tableau的业务分析师,第一反应都是&…

2026/10/14 1:05:04 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/12 18:11:17 阅读更多 →