typescript - 如何避免折叠封装的联合类型
在我看来,当 TypeScript 恰好知道初始值时,它会折叠一个封装的联合类型(这是不希望发生的)。我不确定这是不是一个错误,但我想知道是否有办法避免它。
一个例子:
type Prime = 3|5|7;
type OrDonuts<T> = T | 'donuts';
function compare<T>(a: T, b: OrDonuts<T>) {
return a == b;
}
let value: Prime;
compare(value, 3);
// OK!
value = 5;
compare(value, 3);
// TS2345: Argument of type '5' is not assignable to parameter of type 'OrDonuts<3>'
为了绕过这个错误,我必须明确地展开,例如说 value=5 as Prime;。
这是一个 bug、预期行为,还是我只是做错了?
(节点: 10.15, 类型脚本: 3.5.1)
答案#1
TypeScript 类型检查器执行控制流类型分析,这意味着它会努力确定运行时变量内部的值会发生什么,并会缩小这些变量的类型以匹配。具体的一种方式是,如果你有一个联合类型的变量或属性,并且编译器看到你给它赋了一个更具体类型的值,它会将变量的类型缩小到那个更具体的类型,至少在你为该变量赋其他不同的值之前都是如此。
这通常是很可取的,因为它支持像这样的用例:
let x: number | string = Math.random() < 0.5 ? "hello" : "goodbye"; // x is string now
if (x.length < 6) { // no error here
x = 0; // the widest x can be is string | number, so this assignment is fine
}
请注意,即使你已经标注了x为number | string类型,编译器也能理解在初始赋值之后它肯定是string类型。因此,当你检查x.length时,它不会报错(如果x可能是number类型的话,它就会报错)。这种行为非常有用,以至于禁用它会导致大量实际的 TypeScript 代码出现问题。
不幸的是,它也导致了你在这里看到的行为。在将 5 分配给 value 之后,编译器会将 value 视为包含缩小类型 5 的变量,而不是 Prime。你始终可以将 value 扩展 为 Prime,但编译器不会自动这样做。它认为通过警告你正在调用被禁止的 compare(5 as 5, 3) 函数是在帮助你。
在这种情况下,唯一覆盖此行为的方法是使用类型断言,正如你所看到的。你可以在初始赋值时进行这种断言,或在调用 compare() 时进行:
let value2: Prime = 5 as Prime
compare(value2, 3); // okay
let value3: Prime = 5;
compare(value3 as Prime, 3); // okay
或者,你可以在调用 compare() 时手动指定泛型类型 T,这也可以用:
let value4: Prime = 5;
compare<Prime>(value4, 3); // okay
你可以选择任何这些选项。
我能找到的关于这个的最权威的文档来源是 Microsoft/TypeScript#8513,特别是这条评论。
好的,希望有帮助;祝你好运!
代码链接
答案#2
这是联合类型的预期行为。通过声明
type Prime = 3|5|7; // it is either 3 OR 5 OR 7 but never all of them at the the same time
你告诉 ts 编译器 Prime 是其中一个值。现在你将 5 分配给类型化字段 value。
value = 5; // type Prime is now 5
compare(value, 3); // <T> is inferred by the ts-compiler as 5
为了修复这个问题,你必须使用 value:Prime 或者像你那样进行类型断言。
没有类型推断,你将能够传递 value,例如
value = 5;
compare<2>(value, 3); // Argument of type '5' is not assignable to parameter of type '2'.
或者如果你想以更通用的方式满足 compare 函数参数,比如
let test = 5;
compare(test, 3); // <T> is now number which 5 and 3 are.
