TypeScript中如何跳过泛型函数类型显式定义?API请求场景
TypeScript 泛型函数类型优化方案
问题背景
我们正在为API请求构建一个泛型函数类型,该类型支持payload、response、query parameters、path parameters和config等多个可选泛型参数,代码如下:
export type ApiServiceBaseMethod< P = never, R = void, Q extends QueryParams | undefined = Record<string, string | number>, M extends PathParams | undefined = Record<string, string | number>, C extends ApiServiceMethodConfig = Record<string, string>, > = ({ payload, pathParams, queryParams, config, }: { payload?: P; pathParams?: M; queryParams?: Q; config?: C; }) => Promise<AxiosResponse<R>>;
当前痛点在于:如果仅需指定泛型M(路径参数)的类型,必须手动设置它之前的所有泛型类型为unknown/undefined/never,操作繁琐。由于TypeScript暂不支持跳过泛型参数,需要找到无需定义特定子函数类型变体的解决方法。
解决方案一:用配置对象作为单一泛型参数
将多个泛型参数合并为一个可选的配置对象类型,按需指定需要的泛型属性,无需按顺序填充前面的参数:
// 定义泛型配置的基础结构 type ApiMethodGenerics = { P?: never; R?: void; Q?: QueryParams | undefined; M?: PathParams | undefined; C?: ApiServiceMethodConfig; }; // 处理默认值的工具类型 type ResolveApiMethodGenerics<T extends ApiMethodGenerics = {}> = { P: T['P'] extends never ? never : T['P']; R: T['R'] extends void ? void : T['R']; Q: T['Q'] extends QueryParams | undefined ? T['Q'] : Record<string, string | number>; M: T['M'] extends PathParams | undefined ? T['M'] : Record<string, string | number>; C: T['C'] extends ApiServiceMethodConfig ? T['C'] : Record<string, string>; }; // 重构后的泛型函数类型 export type ApiServiceBaseMethod<T extends ApiMethodGenerics = {}> = ({ payload, pathParams, queryParams, config, }: { payload?: ResolveApiMethodGenerics<T>['P']; pathParams?: ResolveApiMethodGenerics<T>['M']; queryParams?: ResolveApiMethodGenerics<T>['Q']; config?: ResolveApiMethodGenerics<T>['C']; }) => Promise<AxiosResponse<ResolveApiMethodGenerics<T>['R']>>;
使用示例
// 仅指定路径参数类型 type GetUserMethod = ApiServiceBaseMethod<{ M: { userId: string } }>; // 使用该类型 const getUser: GetUserMethod = async ({ pathParams }) => { // pathParams会被推断为{ userId: string } | undefined return axios.get(`/users/${pathParams?.userId}`); }; // 指定多个泛型参数的示例 type CreateUserMethod = ApiServiceBaseMethod<{ P: { name: string; email: string }; R: { userId: string; createdAt: Date }; }>;
解决方案二:利用交叉类型覆盖默认泛型
通过交叉默认泛型和自定义泛型的方式,简化类型定义:
// 定义带有默认值的基础泛型集合 type DefaultApiGenerics = { P: never; R: void; Q: Record<string, string | number>; M: Record<string, string | number>; C: Record<string, string>; }; export type ApiServiceBaseMethod<T extends Partial<DefaultApiGenerics> = {}> = ({ payload, pathParams, queryParams, config, }: { payload?: (T & DefaultApiGenerics)['P']; pathParams?: (T & DefaultApiGenerics)['M']; queryParams?: (T & DefaultApiGenerics)['Q']; config?: (T & DefaultApiGenerics)['C']; }) => Promise<AxiosResponse<(T & DefaultApiGenerics)['R']>>;
使用示例
// 仅指定路径参数类型 type GetUserMethod = ApiServiceBaseMethod<{ M: { userId: string } }>;
这种方式利用TypeScript交叉类型的特性,自定义泛型会覆盖对应位置的默认值,未指定的属性则保留原默认类型。
内容的提问来源于stack exchange,提问作者mmozedev
相关产品推荐
相关产品推荐

