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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 00:49:51