TypeScript 不会通过控制流来缩小泛型类型(请参阅microsoft/TypeScript#24085)。所以即使a 的类型已知为string,T 的类型仍将顽固地保持T。使您的代码按原样编译的唯一方法是使用type assertions 来安抚编译器(如 cmets 中所述):
function concat<T extends string | number>(a: T, b: T): T {
if (isString(a) && isString(b)) {
return a.concat(b) as T; // assert as T
}
return (a as number) + (b as number) as T; // assert as numbers and T
}
警告:当您使用类型断言时,您需要非常小心,不要对编译器撒谎。我们有,从以下情况可以看出:
// string literal types
const oops1 = concat("a", "b");
// type "a" | "b" at compile time, but "ab" at runtime
// numeric literal types
const oops2 = concat(5, 6);
// type 5 | 6 at compile time, but 11 at runtime
// string | number types
let notSure = Math.random() < 0.5 ? "a" : 1
const oops3 = concat(notSure, 100); // no error
// I bet you didn't want concat() to possibly accept string + number
最大的问题是T extends string | number 将prompt 编译器将T 推断为string literal type 或numeric literal type(如果可以)。当您将像"a" 这样的字符串文字作为参数传递时,T 将缩小为"a",这意味着T 只是字符串"a",没有其他值。我猜你不想要那个。
您正在创建的功能类型是您传统上(无论如何在 TS2.8 之前)使用overloads 来完成的功能:
function concat(a: string, b: string): string;
function concat(a: number, b: number): number;
function concat(a: string | number, b: string | number): string | number {
if (isString(a) && isString(b)) {
return a.concat(b);
}
return (a as number) + (b as number);
}
现在这些示例将按照您的预期运行:
const oops1 = concat("a", "b"); // string
const oops2 = concat(5, 6); // number
let notSure = Math.random() < 0.5 ? "a" : 1
const oops3 = concat(notSure, 100); // error, notSure not allowed
您可以使用泛型和conditional types 获得相同的行为,但这可能不值得:
type StringOrNumber<T extends string | number> =
[T] extends [string] ? string :
[T] extends [number] ? number : never
function concat<T extends string | number>(
a: T,
b: StringOrNumber<T>
): StringOrNumber<T> {
if (isString(a) && isString(b)) {
return a.concat(b) as any;
}
return (a as number) + (b as number) as any;
}
const oops1 = concat("a", "b"); // string
const oops2 = concat(5, 6); // number
let notSure = Math.random() < 0.5 ? "a" : 1
const oops3 = concat(notSure, 100); // error