[英]Type Inference for Union Types in TypeScript
说我有以下三个基类或类型:
type A = { one: number };
type B = { one: number, two: string };
type C = { one: number, two: string, three: boolean };
现在,我想创建一个类型化的数组,其中包含许多这些类型的任何实例,因此我声明一个将用于该数组的联合类型 :
type X = A | B | C;
在这里定义数组元素并构造数组:
const a: A = { one: 1 };
const b: B = { one: 1, two: '2' };
const c: C = { one: 1, two: '2', three: true };
let list: X[] = [a, b, c];
现在,如果我尝试访问此数组中第二个或第三个元素的two
属性,则会收到以下错误:
const z: X = list[2];
z.two; // yields this error
error TS2339: Property 'two' does not exist on type 'X'. Property 'two' does not exist on type 'A'.
我试图将A
的类型以及其他类型从const a: A
更改为const a: X
,但是我仍然遇到相同的错误。
如果我将z: X
为z: B
; 我仍然再次收到非常类似的错误:
const z: B = list[2];
z.two; // yields this error
Type 'X' is not assignable to type 'B'. Type 'A' is not assignable to type 'B'. Property 'two' is missing in type 'A'.
在我看来,TypeScript的类型推断机制似乎以某种方式覆盖了我的显式键入,并且基本上不是创建list: X[]
而是创建了一个向下转换的版本,即list: A[]
。
我还尝试使用class
和interface
定义来查看它是否有所不同-即使我确定这不会归因于TypeScript的Structural Type System ,并且按预期没有任何变化。
知道我在这里做错了什么,或者对更好的方法有什么建议吗?
回答
原来将const z: B = list[2]
更改为const z = <B>list[2]
可以正确执行我打算进行的转换。
它并没有覆盖您的显式类型,而是通过将list
视为X
类型的元素数组来服从显式类型。 X
可以是A
, B
或C
如果我给您一个X
类型的值,则可以安全地读取one
属性,因为该属性确实存在。 但是尝试读取two
属性是不安全的 ,因为X
可能是A
,并且不知道A
具有two
。 这样您会得到一个有用的错误:
z.two; // error!
// Property 'two' does not exist on type 'X'. Property 'two' does not exist on type 'A'.
那么,您有什么选择呢? 一种是通过使用另一种答案中的类型断言来告诉编译器您比其了解的更多:
(z as B).two; // okay now
这样可以抑制编译器错误,但这并不是一个很好的解决方案,因为它在不需要时会部分禁用类型检查。 以下内容也不会出错,但是会在运行时给您带来问题:
(list[0] as B).two.length; // no compile error
// at runtime: TypeError: list[0].two is undefined
通常,类型断言应该是处理编译器错误的不得已的方法,仅在无法找到合理方法说服编译器您正在做的事情是安全的,并且您肯定它是安全的并且将保持安全的情况下,才使用类型断言。即使面对可能的代码更改(例如,将来将list
更改为[b,c,a]
)。
更好的解决方案是使用类型防护来说服编译器您正在执行的操作是安全的。 这会对运行时产生影响,因为您正在运行更多代码,但是如果您在list
某个位置更改list
,则您正在运行的代码将更适合未来。 这是一种方法:
const z = list[2]; // z is inferred as A | B | C
if ('two' in z) {
// z is now narrowed to B | C
z.two; // okay
}
因此,通过使用in
来检查two
属性的存在,可以保护z.two
的读取。 现在是安全的。 如果您正在考虑“当我知道z
属于B
类型(实际上是C
,哈哈, list[2]
是第三个元素)时为什么要经历这个麻烦”,然后继续阅读:
如果确定该list
将始终是该类型的A
, B
和C
类型的三元素数组,那么您可以将此告诉编译器,并在编译时获得期望的行为,而无需任何运行时保护。 您正在寻找元组类型 :
const list: [A, B, C] = [a, b, c];
const z = list[2];
z.two; // okay
z.three; // okay
您已经告诉编译器, list
是类型为[A, B, C]
的三元素元组 。 现在没有错误(并且可以看到z
是C
)。 这是安全的,对运行时的影响为零。 它还可以防止您弄乱list
:
list[1] = a; // error! A is not assignable to B
因为您已经告诉它list[1]
始终是B
,所以必须为其分配与B
兼容的内容:
list[1] = c; // okay, C is assignable to B
因此,为您提供三个选项:断言,类型保护和元组,对于这种情况,我建议使用元组。 希望能有所帮助。 祝好运!
您是否尝试过使用类型断言,例如。
const z = <B>list[2]; // omitting the .two because that would never be assignable to B
?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.