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),仅供参考