You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义集成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原生链式调用时:

  1. TypeScript会逐步推断每个中间件对StateCreator的类型修改:
    • immer修改set函数,使其支持生产者函数
    • persist添加持久化相关的类型扩展
    • devtools给set添加第三个action名称参数的重载
  2. createStore接收最终的StateCreator,TypeScript能正确合并所有重载信息

而你的原始自定义函数存在两个问题:

  1. 硬编码了mutator数组为[['zustand/devtools', never], ...Mos],但柯里化调用时Mos无法被正确推断为包含immer和persist的数组
  2. 手动指定泛型参数CountStore时,丢失了后续中间件的类型信息,导致TypeScript默认Mos为空数组,从而丢失set的重载

总结

要避免类型推断失效,你需要:

  1. 让自定义工具的泛型参数自动从传入的StateCreator推断所有mutator类型,不要手动硬编码或省略关键泛型参数
  2. 遵循Zustand的mutator类型叠加规则,确保Mutate类型正确合并所有中间件的类型修改
  3. 优先使用中间件增强器的模式(方案2),更符合Zustand的设计哲学,类型推断也更稳定

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 12:18:03