我遇到打字稿泛型的问题:
function isString(a: any): a is string {
return typeof a === 'string'
}
function concat<T extends string | number>(a: T, b: T): T {
if (isString(a) && isString(b)) {
return a.concat(b)
}
return a + b
}
游乐场网址:https://www.typescriptlang.org/play/index.html#src=function%20isString(a%3A%20any)%3A%20a%20is%20string%20%7B%0D%0A%20%20%20%20return%20typeof%20a%20%3D%3D%3D%20'字符串'%0D%0A%7D%0D%0A%0D%0Afunction%20concat%3CT%20扩展%20string%20%7C%20number%3E(a%3A%20T %2C%20b%3A%20T)%3A%20T%20%7B%0D%0A%20%20%20%20if%20(isString(a)%20%26%26%20isString(b))%20 %7B%0D%0A%20%20%20%20%20%20%20%20return%20a.concat(b)%0D%0A%20%20%20%20%20%7D%0D%0A%20% 20%20%20return%20a%20%2B%20b%0D%0A%7D%0D%0A
键入似乎合适,但我有一些错误。关于打字稿泛型似乎有些困惑,但是我发现的答案都没有帮助我解决这个基本用例。
答案 0 :(得分:2)
TypeScript不narrow generic types via control flow。因此,即使已知a
的类型为string
,T
的类型也会顽固地保持T
。使代码按原样编译的唯一方法是使用type assertions来放置编译器(如注释中所述):
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编译器推断为string literal type或numeric literal type的T
为overloads。当您将诸如"a"
之类的字符串文字作为参数传递时,T
将缩小为"a"
,这意味着T
仅是字符串"a"
而没有其他价值。我想你不想那样。
您要执行的功能是传统上(无论如何在TS2.8之前)使用conditional types完成的功能:
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
使用泛型和{{3}},您可以获得相同的行为,但这可能不值得:
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
无论如何,希望能有所帮助。祝你好运!