如何在TypeScript中创建保留泛型特性与类型的绑定泛型函数?
TypeScript中包装泛型函数时保留类型特性的方案
非泛型函数的正常包装示例
我们可以创建一个包装函数来绑定部分参数,并且保留原函数的类型信息,示例代码如下:
function boundCall<THeadParams extends unknown[], TTailParams extends unknown[], TReturn>( f: (...args: [...THeadParams, ...TTailParams]) => TReturn, ...boundHeadParams: THeadParams ) { return (...tailParams: TTailParams) => { return f(...boundHeadParams, ...tailParams); }; } function noneGeneric(name: string, age: number, gender: "male" | "female") { console.log(name, age, gender); return gender; } const boundNoneGeneric = boundCall(noneGeneric, "classy"); const r1 = boundNoneGeneric(1, "female");
对应的.d.ts文件能正确保留类型:
declare function boundCall<THeadParams extends unknown[], TTailParams extends unknown[], TReturn>( f: (...args: [...THeadParams, ...TTailParams]) => TReturn, ...boundHeadParams: THeadParams ): (...tailParams: TTailParams) => TReturn; declare function noneGeneric(name: string, age: number, gender: "male" | "female"): "male" | "female"; declare const boundNoneGeneric: (age: number, gender: "male" | "female") => "male" | "female"; declare const r1: "male" | "female";
泛型函数包装时的类型丢失问题
当原函数包含泛型参数时,上述boundCall无法保留泛型特性,类型会被降级为unknown:
function genericTest<TLast>(name: string, age: number, gender: "male" | "female", last: TLast) { console.log(name, age, gender); return last; } const boundGeneric = boundCall(genericTest, "classy"); // 此处出现类型错误 const r2 = boundGeneric(1, "female", { test: true });
生成的.d.ts文件中,boundGeneric的类型不符合预期:
declare function boundCall<THeadParams extends unknown[], TTailParams extends unknown[], TReturn>( f: (...args: [...THeadParams, ...TTailParams]) => TReturn, ...boundHeadParams: THeadParams ): (...tailParams: TTailParams) => TReturn; declare function genericTest<TLast>(name: string, age: number, gender: "male" | "female", last: TLast): TLast; declare const boundGeneric: (...tailParams: unknown[]) => unknown; declare const r2: unknown;
我们预期boundGeneric应该是一个泛型函数:
type boundGeneric = <TLast>(age: number, gender: "male" | "female", last: TLast) => TLast;
解决方法:让包装函数支持泛型函数
要解决这个问题,需要修改boundCall的类型定义,使其能够识别并保留原函数的泛型参数。核心思路是让类型系统将原函数视为泛型函数类型,而非拆解为固定的参数列表和返回值:
// 类型定义 function boundCall<F extends (...args: any) => any>( f: F, ...boundArgs: Parameters<F> extends [...infer B, ...any] ? B : never ): Parameters<F> extends [...infer B, ...infer R] ? B extends typeof boundArgs ? <T>(...args: R) => ReturnType<F> : never : never; // 实现部分保持不变 function boundCall(f: (...args: any[]) => any, ...boundArgs: any[]) { return (...tailArgs: any[]) => f(...boundArgs, ...tailArgs); }
使用修改后的boundCall处理泛型函数:
function genericTest<TLast>(name: string, age: number, gender: "male" | "female", last: TLast) { console.log(name, age, gender); return last; } const boundGeneric = boundCall(genericTest, "classy"); const r2 = boundGeneric(1, "female", { test: true }); // r2类型为{ test: true }
此时生成的.d.ts文件会正确保留泛型特性:
declare function boundCall<F extends (...args: any) => any>( f: F, ...boundArgs: Parameters<F> extends [...infer B, ...any] ? B : never ): Parameters<F> extends [...infer B, ...infer R] ? B extends typeof boundArgs ? <T>(...args: R) => ReturnType<F> : never : never; declare function genericTest<TLast>(name: string, age: number, gender: "male" | "female", last: TLast): TLast; declare const boundGeneric: <TLast>(age: number, gender: "male" | "female", last: TLast) => TLast; declare const r2: { test: true };
内容的提问来源于stack exchange,提问作者ClassY
相关产品推荐
相关产品推荐

