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

如何创建可根据首个参数类型推导次参数类型的TypeScript泛型函数

实现类型安全的addT函数需求

需要创建一个addT函数,满足:

  • 第一个参数a类型为string | number
  • 第二个参数b的类型必须与a的类型完全一致
  • 无需手动传入泛型参数,由TypeScript自动推导类型

几种失败的尝试及问题分析

尝试1:简单泛型约束报错

const addT = <T extends string | number>(a, b) => a + b
//                                                ^^^^^
// 错误:运算符 '+' 不能应用于类型 'T' 和 'T'。(2365)

addT(1,3)
addT("","")

TypeScript无法确定泛型T具体是string还是number,因为+运算符对两种类型的行为不同,所以不允许直接使用。

尝试2:重载类型导致参数隐式any

type TAdd2 = {
  // 数字类型重载
  <T extends number>(a: T, b: T): T;
  // 字符串类型重载
  <T extends string>(a: T, b: T): T;
};

const add: TAdd2 = (a, b) => a + b;
//                  ^  ^
// 错误:参数 'a' 隐式具有 'any' 类型。(7006)
// 错误:参数 'b' 隐式具有 'any' 类型。(7006)

add("", "")
add(1, 2)

重载签名的实现函数无法自动继承重载的参数类型,必须显式指定参数类型才能避免any报错。

尝试3:默认泛型导致类型固定为string

// 问题:TAdd3 默认将泛型参数设为 string
type TAdd3<T1 extends string | number = string, T2 extends string | number = string> = 
  (a: T1, b: T2) => (T1 extends number ? (T2 extends number ? number : never) : string);
const add3: TAdd3 = (a, b) => a + b;
add3("", "")
add3(1,2)
//   ^
// 错误:类型 'number' 的参数不能赋给类型 'string' 的参数。(2345)

默认泛型参数让TAdd3在未手动指定类型时默认使用string,导致无法正确推导数字类型的调用。

尝试4:柯里化导致字面量类型过窄

const add4 = <T extends number | string>(a: T) => (b: T) => 
  typeof a === 'string' ?  a + b : `${a}${b}`;
add4(1)(3)
//      ^
// 错误:类型 '3' 的参数不能赋给类型 '1' 的参数。(2345)

泛型T被推导成了字面量类型1(而不是number),导致第二个参数只能接受和第一个参数完全相同的字面量值,不符合需求。


正确实现方案

方案1:泛型函数+类型守卫

利用类型守卫让TypeScript明确分支内的参数类型,同时用条件类型指定返回值:

const addT = <T extends string | number>(a: T, b: T): T extends string ? string : number => {
  if (typeof a === 'string') {
    return (a + b) as T extends string ? string : number;
  }
  return (a + b) as T extends string ? string : number;
};

// 测试用例
addT(1, 3); // 推导为 number 类型
addT("hello", " world"); // 推导为 string 类型
addT(1, "2"); // 报错:类型不匹配,符合预期
  • 泛型T约束为string | number,确保两个参数类型一致
  • 类型守卫typeof a === 'string'让TypeScript在分支中确认参数具体类型,允许使用+运算符
  • 条件类型确保返回值类型与参数类型匹配

方案2:重载函数(修正参数类型)

显式定义重载签名,并给实现函数指定参数类型:

function addT(a: number, b: number): number;
function addT(a: string, b: string): string;
function addT(a: string | number, b: string | number): string | number {
  return a + b;
}

// 测试用例
addT(1, 3); // number
addT("hello", "world"); // string
addT(1, "2"); // 报错:参数类型不匹配,符合预期
  • 重载签名明确了两种合法调用场景:两个数字或两个字符串
  • 实现函数的参数类型覆盖所有重载情况,避免any类型
  • TypeScript会根据调用参数自动匹配对应的重载,推导正确返回类型

方案3:简化泛型实现

利用类型守卫和类型断言简化代码,让TypeScript自动推导返回值:

const addT = <T extends string | number>(a: T, b: T) => {
  if (typeof a === 'string') {
    return a + b as T;
  }
  return a + b as T;
};

// 测试用例
const numResult = addT(1, 3); // number
const strResult = addT("a", "b"); // string
  • 类型守卫确保分支内的参数类型安全
  • as T断言返回值与参数类型一致,代码更简洁

内容的提问来源于stack exchange,提问作者Highmastdon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 16:44:56