如何为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
相关产品推荐
相关产品推荐

