尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Zustand `combine` 中间件完全指南:自动推断类型的状态合并方案

Zustand `combine` 中间件完全指南:自动推断类型的状态合并方案 Zustandcombine中间件完全指南自动推断类型的状态合并方案【免费下载链接】zustand Bear necessities for state management in React项目地址: https://gitcode.com/gh_mirrors/zu/zustandcombine是 Zustand 官方提供的一个状态创建辅助中间件它通过将初始状态与补充状态与 actions 创建函数合并让开发者无需手写任何类型定义即可获得完整的 TypeScript 类型推断。本文基于仓库中的 API 参考文档 编写并结合 源码实现、类型测试用例及相关指南深入讲解其签名、底层合并原理、典型使用场景与类型安全边界帮助你在 React 与纯 vanilla 场景中放心使用这一风格。一、combine是什么combine中间件用于创建一个内聚的 store把**初始状态initial state与一个补充状态创建函数additional state creator**合并在一起。补充函数负责追加新的状态片段state slices与 actions。由于最终状态类型由combine自动推导你完全不需要显式声明interface/type来标注 state 类型。[!TIP] 使用combine后create与createStore的curried柯里化版本不再是必需的——这正是它的核心价值之一让中间件使用更直接、更高效。这一点在 TypeScript 高级指南 中也有说明combine本身就创建了状态因此无需再用createState()(...)的柯里化形式来手动注入类型参数。二、类型签名SignaturecombineT extends object, U extends object(initialState: T, additionalStateCreatorFn: StateCreatorT, [], [], U): StateCreatorOmitT, keyof U U, [], []拆解这一签名见 combine.ts 源码T extends objectinitialState的类型必须是对象类型U extends objectadditionalStateCreatorFn返回值即补充的状态与 actions的类型StateCreatorT, [], [], U说明补充函数接收基于状态T的set、get、store参数并返回U返回类型StateCreatorOmitT, keyof U U, [], []OmitT, keyof U U表示取T中未被U覆盖的键再与U求交集即最终状态是二者的**浅合并shallow merge**类型。源码中的type WriteT, U OmitT, keyof U U正是这一逻辑的别名。三、参数与返回值Referencecombine(initialState, additionalStateCreatorFn)参数initialStatestore 的初始值。可以是任意类型的值唯独不能是函数函数会被误判为创建函数additionalStateCreatorFn一个接收set、get与store三个参数的函数对应 vanilla 的StateCreator约定。通常你会在其中返回一个包含想要暴露的方法actions的对象。返回值返回一个状态创建函数state creator function可直接传给create或createStore。四、运行时实现原理源码级combine的实现极其精简核心只有一行src/middleware/combine.tsreturn (...args) Object.assign({}, initialState, (create as any)(...args))这意味着先以{}为基底展开initialState再执行补充创建函数得到的状态对象并浅合并覆盖同名字段。因此当U中某个键与T相同名时补充函数的返回值胜出这与类型签名中OmitT, keyof U U的语义完全一致。最终合并出的对象会被作为完整状态传给底层的 createStoreImpl后者再把它与setState、getState、subscribe、getInitialState一起组成StoreApi。另外注意combine返回的创建函数同样透传set、get与store源码中即...args的完整转发因此合并后的状态创建函数与普通创建函数在使用上没有任何区别。五、使用示例创建自动推断类型的 store以下示例来自 API 参考文档用createStorevanilla 版追踪指针移动把圆点定位到鼠标位置全程无需任何显式类型标注。import { createStore } from zustand/vanilla import { combine } from zustand/middleware const positionStore createStore( combine({ position: { x: 0, y: 0 } }, (set) ({ setPosition: (position) set({ position }), })), ) const $dotContainer document.getElementById(dot-container) as HTMLDivElement const $dot document.getElementById(dot) as HTMLDivElement $dotContainer.addEventListener(pointermove, (event) { positionStore.getState().setPosition({ x: event.clientX, y: event.clientY, }) }) const render: Parameterstypeof positionStore.subscribe[0] (state) { $dot.style.transform translate(${state.position.x}px, ${state.position.y}px) } render(positionStore.getInitialState(), positionStore.getInitialState()) positionStore.subscribe(render)配套的 HTMLdiv iddot-container styleposition: relative; width: 100vw; height: 100vh; div iddot styleposition: absolute; background-color: red; border-radius: 50%; left: -10px; top: -10px; width: 20px; height: 20px; /div /div要点说明combine自动推导出状态类型{ position: { x: number; y: number }; setPosition: (position: { x: number; y: number }) void }因此setPosition的参数类型、render订阅回调的state.position都具备完整的智能提示订阅回调类型直接借用Parameterstypeof positionStore.subscribe[0]保持与 store 类型同步避免手写漂移这里刻意用getInitialState()完成首次渲染订阅前随后通过subscribe持续响应指针移动。React 场景下的用法combine同样适用于 React 的create钩子。参考 TypeScript 入门指南状态与 actions 分离类型完全自动推断import { create } from zustand import { combine } from zustand/middleware export const useBearStore create( combine({ bears: 0 }, (set) ({ increase: () set((s) ({ bears: s.bears 1 })), })), )六、与其他中间件组合combine是创建状态型中间件可以被其他变换型中间件如devtools、subscribeWithSelector包裹使用。仓库的 类型测试 覆盖了多种组合见devtools combine、subscribeWithSelector combine、devtools subscribeWithSelector combine等用例tests/middlewareTypes.test.tsx例如import { create } from zustand import { devtools, subscribeWithSelector, combine } from zustand/middleware const useBoundStore create( devtools( subscribeWithSelector( combine({ count: 1 }, (set, get) ({ inc: () set({ count: get().count 1 }, false), })), ), ), )注意像devtools、persist、subscribeWithSelector这类中间件通常通过 curried 形式配合显式状态类型使用而combine属于创建状态的中间件同类的还有redux当你使用它们时就不必再写柯里化版本因为状态已由combine推断出来见 advanced-typescript.md 中的说明。如果确实需要在 store 声明之外单独取出状态类型可以使用ExtractState类型助手import { create, ExtractState } from zustand import { combine } from zustand/middleware type BearState ExtractStatetypeof useBearStore const useBearStore create( combine({ bears: 0 }, (set) ({ increase: (by: number) set((state) ({ bears: state.bears by })), })), )ExtractState定义见 src/vanilla.tsS extends { getState: () infer T } ? T : never。七、类型推断机制的小谎言与注意事项combine之所以能自动推断是因为它在类型层面对传入的set、get、store做了巧妙的近似处理详见 advanced-typescript.md 的警告段落在补充创建函数内部set/get/store被假装类型为仅初始状态T而实际最终状态是二者的浅合并{ ...initialState, ...additional }例如get在函数内的返回类型是() { bears: number }而真实运行时返回的是{ bears: number, increase: (by: number) void }由于{ bears: number }是最终状态的子类型这种近似在绝大多数场景下是**健全sound**的不会引发问题。但请警惕以下两个容易踩坑的场景set(..., true)全量替换set({ bears: 0 }, true)会通过编译但运行时会把increase等函数一并删除造成状态损坏底层setState在replace为真时直接整体替换见 src/vanilla.tsObject.keys(get())在补充函数内用Object.keys会返回[bears, increase]而不是[bears]因为运行时对象确实包含 actions而get的近似返回类型可能让你误判。此外需要留意initialState不能是函数且由于合并是浅合并嵌套对象如示例中的position对象需要整体替换或使用不可变更新这与 Zustand 一贯的不可变更新约定一致。八、测试与证据仓库在 tests/middlewareTypes.test.tsx 中为combine提供了类型级验证断言useBoundStore((s) s.count) * 2的类型为number、s.inc()的类型为void并验证combine({ count: 0 }, () ({}))的结果仍可赋值给StoreApiobject。这些用例保证了无需显式类型、推断结果可用的核心承诺。九、Troubleshooting参考文档中该小节标记为TBD待补充。结合源码与指南可先记住以下排查要点类型推断失败确认initialState不是函数且补充创建函数返回的是一个对象字面量而非条件分支导致类型宽泛化运行时多了/少了字段检查是否用了set(..., true)全量替换或Object.keys依赖了get()的字段与持久化配合异常combine与persist组合时持久化的 merge 逻辑以持久化层的配置为准注意与combine的浅合并语义区分。总体而言combine用极小的实现代价换来了零类型样板代码的开发体验非常适合状态结构清晰、希望保持代码简洁的 TypeScript 项目——正如 TypeScript 入门指南 所说这是一种在 TS 项目中非常流行的写法。【免费下载链接】zustand Bear necessities for state management in React项目地址: https://gitcode.com/gh_mirrors/zu/zustand创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表