自定义集成persist、immer、devtools的Zustand createStore工具导致set函数的action类型定义失效
自定义集成persist、immer、devtools的Zustand createStore工具导致set函数的action类型定义失效
问题分析
你遇到的核心问题是自定义createDevtoolsStore函数破坏了Zustand的mutator类型链,导致TypeScript无法正确推断set函数的重载(支持第三个action名称参数)。这是因为你的泛型类型定义没有正确跟踪后续immer和persist中间件对StoreApi和set函数的类型修改,而直接链式调用Zustand原生方法时,TypeScript可以自动递归叠加每个中间件的类型变更。
解决方案
我们需要调整自定义工具的泛型定义,让它正确传递所有mutator的类型信息,同时保留Zustand的类型推断能力。以下是两种可行的修复方案:
方案1:修复自定义createDevtoolsStore的泛型类型
重新定义函数,让TypeScript自动推断所有中间件的mutator类型,正确叠加devtools、immer和persist对set函数的类型修改:
import { StateCreator, StoreApi, createStore, Mutate } from 'zustand'; import { devtools, DevtoolsOptions } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; import { persist, PersistOptions } from 'zustand/middleware/persist'; // 定义你的Store类型 type CountStore = { count: number; actions: { increment: () => void; }; }; // 修复后的createDevtoolsStore:自动推断所有mutator类型 export function createDevtoolsStore< T, M extends [StoreMutatorIdentifier, unknown][] = [] >( initializer: StateCreator<T, [], M>, devtoolsOptions?: DevtoolsOptions ): Mutate<StoreApi<T>, [['zustand/devtools', never], ...M]> { return createStore<T, [], [['zustand/devtools', never], ...M]>( devtools(initializer, { enabled: process.env.NODE_ENV !== 'production', ...devtoolsOptions, }) ); } // 正确创建store:TypeScript自动推断mutator类型 export const createCountStore = () => { return createDevtoolsStore( persist( immer((set) => ({ count: 0, actions: { increment: () => set( (state) => { state.count += 1; }, false, 'increment' // 现在TypeScript能正确识别第三个参数 ), }, })), { name: 'count-store' } ) ); };
方案2:封装为中间件增强器(更符合Zustand设计风格)
如果你更倾向于Zustand原生的中间件链式调用模式,可以将devtools封装为一个可复用的增强器,这样类型推断会更自然:
import { StateCreator } from 'zustand'; import { devtools, DevtoolsOptions } from 'zustand/middleware'; // 封装devtools为可复用增强器 export const withDevtools = <T>( initializer: StateCreator<T, [], []>, devtoolsOptions?: DevtoolsOptions ): StateCreator<T, [], [['zustand/devtools', never]]> => { return devtools(initializer, { enabled: process.env.NODE_ENV !== 'production', ...devtoolsOptions, }); }; // 使用时与其他中间件链式调用 export const createCountStore = () => { return createStore( persist( immer( withDevtools((set) => ({ count: 0, actions: { increment: () => set( (state) => { state.count += 1; }, false, 'increment' // 类型推断正常 ), }, })) ), { name: 'count-store' } ) ); };
为什么直接链式调用有效,自定义函数失效?
当你直接使用Zustand原生链式调用时:
- TypeScript会逐步推断每个中间件对
StateCreator的类型修改:immer修改set函数,使其支持生产者函数persist添加持久化相关的类型扩展devtools给set添加第三个action名称参数的重载
createStore接收最终的StateCreator,TypeScript能正确合并所有重载信息
而你的原始自定义函数存在两个问题:
- 硬编码了mutator数组为
[['zustand/devtools', never], ...Mos],但柯里化调用时Mos无法被正确推断为包含immer和persist的数组 - 手动指定泛型参数
CountStore时,丢失了后续中间件的类型信息,导致TypeScript默认Mos为空数组,从而丢失set的重载
总结
要避免类型推断失效,你需要:
- 让自定义工具的泛型参数自动从传入的
StateCreator推断所有mutator类型,不要手动硬编码或省略关键泛型参数 - 遵循Zustand的mutator类型叠加规则,确保
Mutate类型正确合并所有中间件的类型修改 - 优先使用中间件增强器的模式(方案2),更符合Zustand的设计哲学,类型推断也更稳定
内容来源于stack exchange
相关产品推荐
相关产品推荐

