TypeScript如何保留泛型约束提取泛型函数参数类型
提取泛型函数参数时保留类型约束的无冗余变量实现
问题场景
使用第三方库时存在如下类型定义:
type ParamsMap = { method1: { prop1: string, }, method2: { prop1: number, prop2: string, } } type Methods = keyof ParamsMap; type Query = <K extends Methods>(method: K, params: ParamsMap[K]) => void;
该库提供了上述类型的实现,典型调用模式如下:
const query: Query = (method, params) => { // 内部逻辑 console.log(`${method} : ${JSON.stringify(params)}`); }; query("method1", { prop1: "1234" });
需求是提取query函数的参数类型,用于在底层调用时增加额外处理逻辑,返回值类型无要求。
原生Parameters工具的缺陷
直接使用TypeScript内置的Parameters提取参数时,会丢失method与params之间的绑定约束:
/* 提取得到的类型: [method: keyof ParamsMap, params: { prop1: string; } | { prop1: number; prop2: string; }] */ type ExtractQueryParams = Parameters<Query> const test1 = (...params: ExtractQueryParams) => { console.log(`${JSON.stringify(params)}`); } // 非法传参不会触发报错:method1传入了method2的参数 test1("method1", { prop1: 1, prop2: "sdfs" });
该结果不符合类型安全要求,方法名和参数的对应关系失效。
现有方案的不足
目前可通过额外声明Query类型的占位变量,在typeof子句中传入泛型参数提取保留约束的类型:
let q: Query; // 提取结果为 <K extends keyof ParamsMap> = [method: K, params: ParamsMap[K]] type ExtractQueryParamsWithVar<K extends Methods> = Parameters<typeof q<K>> const test2 = <K extends Methods>(...params: ExtractQueryParamsWithVar<K>) => { console.log(`${JSON.stringify(params)}`); } // 非法传参正常触发类型报错 test2("method1", { prop1: 1, prop2: "sdfs" });
该方案可以满足类型校验要求,但需要额外声明无实际用途的冗余变量。
无冗余变量的最优实现
场景化最简方案
针对当前固定的Query类型签名,不需要借助Parameters做类型提取,直接基于原类型的泛型约束构造参数元组即可,代码量最少且类型完全对齐:
// 直接构造保留约束的参数类型,无任何冗余声明 type QueryParams<K extends Methods> = [method: K, params: ParamsMap[K]]; const test3 = <K extends Methods>(...params: QueryParams<K>) => { console.log(`${JSON.stringify(params)}`); } // 非法传参正常报错 test3("method1", { prop1: 1, prop2: "sdfs" }); // 合法传参正常通过 test3("method1", { prop1: "valid" }); test3("method2", { prop1: 1, prop2: "valid" });
该方案完全复用了原Query类型的泛型约束逻辑,不存在类型偏差,也不需要声明任何占位变量。
通用泛型提取方案
如果需要适配任意泛型函数的参数提取,可通过条件类型推断实现通用工具类型,同样不需要额外变量:
// 通用泛型函数参数提取工具 type ExtractGenericParams<F, T> = F extends (arg1: infer A, ...args: infer R) => any ? T extends A ? [method: T, ...{ [I in keyof R]: R[I] extends Record<string, any> ? ParamsMap[T & keyof ParamsMap] : never }] : never : never; // 针对Query类型使用 type GenericQueryParams<K extends Methods> = ExtractGenericParams<Query, K>;
对于绝大多数明确知道泛型函数签名结构的业务场景,直接构造参数元组的方案可读性和维护性更高。
内容的提问来源于stack exchange,提问作者Vadim
相关产品推荐
相关产品推荐

