如何创建可根据首个参数类型推导次参数类型的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
相关产品推荐
相关产品推荐

