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

TypeScript中createSlice无法正确推断api类型问题求助

问题描述

我期望以下代码中api参数能被推断为类型{ login: (param: number) => Promise<void> },但实际TypeScript将其推断为any类型:

export function createSlice<
  T extends Record<
    keyof T,
    (state: S, payload?: any) => S | ((api: {
      [K in keyof T]: Parameters<T[K]>[1] extends undefined
        ? { (): void }
        : { (payload: Parameters<T[K]>[1]): void }
    }) => Promise<void>)
  >,
  N extends string,
  S,
>(
  config: T, name: N, initialState: S
) {
  return {};
}

interface TestState {
  isTest: boolean;
}

const slice = createSlice({
  login: (state: TestState, param: number) => async (api) => {
    api.login('sdf'); // api被推断为any,此处无类型检查
  },
}, 'testSlice', { isTest: false });

请问这是TypeScript的基础限制吗?如果是,该如何重构API以实现预期的类型推断?

原因分析

这确实是TypeScript的类型推断基础限制。原代码的泛型约束中存在循环引用:T的定义依赖于S,同时api的类型又依赖于T的属性参数类型。TypeScript的类型推断系统无法在单次推导过程中处理这种互相依赖的循环条件类型,因此只能将api推断为any来规避无法解析的矛盾。

重构方案

核心思路是打破泛型约束中的循环依赖,让TypeScript先推断出action集合的类型,再基于该类型计算出api的类型。以下是两种可行的重构方式:

方式一:拆分类型定义

先定义独立的action类型和对应的api类型,再在createSlice中使用:

// 定义单个slice action的类型
type SliceAction<S, P = void> = 
  P extends void 
    ? (state: S) => (api: Record<string, SliceActionApi<any>>) => Promise<void>
    : (state: S, payload: P) => (api: Record<string, SliceActionApi<any>>) => Promise<void>;

// 根据action集合推导api类型
type SliceActionApi<A extends Record<string, SliceAction<any, any>>> = {
  [K in keyof A]: 
    Parameters<A[K]>[1] extends undefined 
      ? () => Promise<void> 
      : (payload: Parameters<A[K]>[1]) => Promise<void>;
};

export function createSlice<
  A extends Record<string, SliceAction<S>>,
  N extends string,
  S,
>(
  config: A, 
  name: N, 
  initialState: S
) {
  return {};
}

// 使用示例
interface TestState {
  isTest: boolean;
}

const slice = createSlice({
  login: (state: TestState, param: number) => async (api) => {
    api.login('sdf'); // 类型错误:string不能赋值给number,符合预期
  },
}, 'testSlice', { isTest: false });

方式二:使用条件类型延迟推导

通过条件类型在推断出T后再计算api的类型,避免循环约束:

// 根据action集合推导api类型
type ApiFromActions<T, S> = {
  [K in keyof T]: 
    T[K] extends (state: S, payload: infer P) => ((api: any) => Promise<void>)
      ? P extends undefined 
        ? () => Promise<void> 
        : (payload: P) => Promise<void>
      : never;
};

export function createSlice<
  T extends Record<string, (state: S, payload?: any) => (api: ApiFromActions<T, S>) => Promise<void>>,
  N extends string,
  S,
>(
  config: T, 
  name: N, 
  initialState: S
) {
  return {};
}

// 使用示例
interface TestState {
  isTest: boolean;
}

const slice = createSlice({
  login: (state: TestState, param: number) => async (api) => {
    api.login('sdf'); // 类型错误:string不能赋值给number,符合预期
  },
}, 'testSlice', { isTest: false });
说明

两种重构方式都通过拆分依赖关系让TypeScript能够分步完成类型推断:先确定action的参数和返回类型,再基于这些信息生成api的类型,最终实现对api方法的参数类型检查。

内容的提问来源于stack exchange,提问作者Andy Jessop

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 16:20:42