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

如何为TypeScript类动态定义带输入输出类型的方法

解决方案

核心思路

要实现类方法的输入输出类型自动推导,需借助TypeScript映射类型将请求定义转换为方法签名,同时通过接口或抽象类约束类的结构。类的成员不支持直接使用映射类型,因此需要先通过类型别名生成方法结构,再让类实现该结构。

步骤1:定义请求结构类型

拆分URL路径参数、请求体和响应类型,让每个方法的输入输出一目了然:

interface RequestDefinition<TParams = void, TBody = void, TOutput = unknown> {
  // URL路径参数(如 /users/:id 中的 id)
  params?: TParams;
  // HTTP请求体数据
  body?: TBody;
  // 接口响应类型
  response: TOutput;
}

步骤2:生成方法签名映射类型

通过条件类型动态生成方法参数,根据params和body是否存在自动调整参数列表:

type RequestStoreMethods<TRequests extends Record<string, RequestDefinition>> = {
  [MethodName in keyof TRequests]: (
    // 替换为你的HTTP客户端类型,比如 AxiosInstance
    http: any,
    ...args: 
      TRequests[MethodName]['params'] extends void 
        ? (TRequests[MethodName]['body'] extends void ? [] : [body: TRequests[MethodName]['body']])
        : (TRequests[MethodName]['body'] extends void ? [params: TRequests[MethodName]['params']] : [params: TRequests[MethodName]['params'], body: TRequests[MethodName]['body']])
  ) => Promise<TRequests[MethodName]['response']>;
};

步骤3:实现实例方法版本(推荐)

通过抽象基类约束子类,实现类型自动推导:

abstract class RequestStore<TRequests extends Record<string, RequestDefinition>> implements RequestStoreMethods<TRequests> {
  // 抽象签名,强制子类实现所有方法
  abstract [key: string]: (http: any, ...args: any[]) => Promise<any>;
}

// 使用示例
import { MemberViewModel, CreateMemberViewModel } from "./your-models";

class MemberRequests extends RequestStore<{
  getAllMembers: RequestDefinition<void, void, MemberViewModel[]>;
  getMemberById: RequestDefinition<{id: number}, void, MemberViewModel>;
  createMember: RequestDefinition<void, CreateMemberViewModel, number>;
}> {
  async getAllMembers(http: any) {
    // 实际请求逻辑,比如 await http.get('/members')
    return [] as MemberViewModel[];
  }

  async getMemberById(http: any, {id}: {id: number}) {
    return {id, name: "测试用户"} as MemberViewModel;
  }

  async createMember(http: any, body: CreateMemberViewModel) {
    // 实际请求逻辑,比如 await http.post('/members', body)
    return 1001; // 返回新创建的用户ID
  }
}

步骤4:静态方法版本(适配你的示例)

如果需要用静态方法,通过接口约束类的静态结构:

// 复用之前的RequestStoreMethods类型作为静态方法约束
type RequestStoreStaticMethods<TRequests extends Record<string, RequestDefinition>> = RequestStoreMethods<TRequests>;

// 使用示例
interface MemberRequestsStatic extends RequestStoreStaticMethods<{
  getAllMembers: RequestDefinition<void, void, MemberViewModel[]>;
  getMemberById: RequestDefinition<{id: number}, void, MemberViewModel>;
  createMember: RequestDefinition<void, CreateMemberViewModel, number>;
}> {}

abstract class MemberRequests implements MemberRequestsStatic {
  public static async getAllMembers(http: any) {
    return [] as MemberViewModel[];
  }

  public static async getMemberById(http: any, {id}: {id: number}) {
    return {id: 1, name: "测试用户"} as MemberViewModel;
  }

  public static async createMember(http: any, body: CreateMemberViewModel) {
    return 2;
  }
}

解决你之前的问题

  • 尝试1的问题:索引签名的泛型是全局的,无法绑定到具体方法,导致每个方法的返回类型无法预定义,类型推导失效。
  • 尝试2的问题:TypeScript不允许在类的成员中直接使用[K in keyof TRequests]这类映射类型,映射类型仅支持用于类型别名或接口。

额外优化

将http: any替换为具体的HTTP客户端类型(如AxiosInstance),进一步提升类型安全性:

import type { AxiosInstance } from 'axios';

type RequestStoreMethods<TRequests extends Record<string, RequestDefinition>> = {
  [MethodName in keyof TRequests]: (
    http: AxiosInstance,
    ...args: /* 同之前的条件类型 */
  ) => Promise<TRequests[MethodName]['response']>;
};

内容的提问来源于stack exchange,提问作者JK Effects

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 18:14:57