简体   繁体   English

TypeScript中联合类型的类型推断

[英]Type Inference for Union Types in TypeScript

Say I have three base classes or types as below: 说我有以下三个基类或类型:

type A = { one: number };
type B = { one: number, two: string };
type C = { one: number, two: string, three: boolean };

Now I would like to create a typed array that holds a number any of instances of these types, so I declare a Union Type which will be used for the array: 现在,我想创建一个类型化的数组,其中包含许多这些类型的任何实例,因此我声明一个将用于该数组的联合类型

type X = A | B | C;

Defining the array elements here and constructing the array: 在这里定义数组元素并构造数组:

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];

Now if I try to access the two property of the second or third element in this array, I get the following error: 现在,如果我尝试访问此数组中第二个或第三个元素的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'.

I have tried changing the type of A -- and others in fact, from const a: A to const a: X , but I still get the same error. 我试图将A的类型以及其他类型从const a: A更改为const a: X ,但是我仍然遇到相同的错误。

If I cast z: X to z: B instead; 如果我将z: Xz: B ; I still get a very similar error again: 我仍然再次收到非常类似的错误:

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'.

To me it does seem that TypeScript's type inference mechanism is somehow overrides my explicit typing and basically instead of creating list: X[] it has created a down-casted version which is list: A[] . 在我看来,TypeScript的类型推断机制似乎以某种方式覆盖了我的显式键入,并且基本上不是创建list: X[]而是创建了一个向下转换的版本,即list: A[]

I have also tried using class and interface definition to see if it makes a difference -- even though I was sure it wouldn't due to TypeScript's Structural Type System , and as expected nothing changed. 我还尝试使用classinterface定义来查看它是否有所不同-即使我确定这不会归因于TypeScript的Structural Type System ,并且按预期没有任何变化。

Any idea what I'm doing wrong here or any suggestions for a better approach? 知道我在这里做错了什么,或者对更好的方法有什么建议吗?

Answer 回答

It turned-out change const z: B = list[2] to const z = <B>list[2] does the proper casting that I had the intention of. 原来将const z: B = list[2]更改为const z = <B>list[2]可以正确执行我打算进行的转换。

It's not overriding your explicit typing... it's obeying your explicit typing by treating list as an array of elements of type X . 它并没有覆盖您的显式类型,而是通过将list视为X类型的元素数组来服从显式类型。 An X can be either an A , a B , or a C . X可以是ABC If I hand you a value of type X , it is safe to read the one property, because that definitely exists. 如果我给您一个X类型的值,则可以安全地读取one属性,因为该属性确实存在。 But it is unsafe to try to read the two property, because the X might be an A , and A is not known to have a two . 但是尝试读取two属性是不安全的 ,因为X可能是A ,并且不知道A具有two So you get a useful error: 这样您会得到一个有用的错误:

z.two; // error!
// Property 'two' does not exist on type 'X'. Property 'two' does not exist on type 'A'.

So, what are your options? 那么,您有什么选择呢? One is to just tell the compiler that you know better than it does, by using a type assertion as in the other answer: 一种是通过使用另一种答案中的类型断言来告诉编译器您比其了解的更多:

(z as B).two; // okay now

This suppresses the compiler error, but that really isn't a great solution because it is partially disabling type checking when it doesn't need to. 这样可以抑制编译器错误,但这并不是一个很好的解决方案,因为它在不需要时会部分禁用类型检查。 The following also will not be an error, but it would give you problems at runtime: 以下内容也不会出错,但是会在运行时给您带来问题:

(list[0] as B).two.length;  // no compile error
// at runtime: TypeError: list[0].two is undefined

Usually, type assertions should be a last resort of dealing with compiler errors, to be used only where you cannot find a reasonable way to convince the compiler that what you are doing is safe, and you are positive that it is safe and will remain safe even in the face of possible code changes (eg, you change list to [b,c,a] in the future). 通常,类型断言应该是处理编译器错误的不得已的方法,仅在无法找到合理方法说服编译器您正在做的事情是安全的,并且您肯定它是安全的并且将保持安全的情况下,才使用类型断言。即使面对可能的代码更改(例如,将来将list更改为[b,c,a] )。


A better solution is to use type guards to convince the compiler that what you are doing is safe. 更好的解决方案是使用类型防护来说服编译器您正在执行的操作是安全的。 This has a run-time impact, in that you are running more code, but the code you are running is more future-proof if you change list somewhere down the line. 这会对运行时产生影响,因为您正在运行更多代码,但是如果您在list某个位置更改list ,则您正在运行的代码将更适合未来。 Here's a way to do it: 这是一种方法:

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
}

So you are guarding the read of z.two by using in to check the presence of the two property before using it. 因此,通过使用in来检查two属性的存在,可以保护z.two的读取。 This is now safe. 现在是安全的。 If you are thinking "why should I go through this trouble when I know that z will be of type B (actually C , ha ha, list[2] is the third element)", then read on: 如果您正在考虑“当我知道z属于B类型(实际上是C ,哈哈, list[2]第三个元素)时为什么要经历这个麻烦”,然后继续阅读:


If you are sure that list will always be a three-element array of types A , B , and C , in that order, then you can tell the compiler this and get the behavior you expect at compile time without any runtime guarding. 如果确定该list将始终是该类型的ABC类型的三元素数组,那么您可以将此告诉编译器,并在编译时获得期望的行为,而无需任何运行时保护。 You are looking for tuple types : 您正在寻找元组类型

const list: [A, B, C] = [a, b, c];

const z = list[2];
z.two; // okay
z.three; // okay

You've told the compiler that list is a three-element tuple of type [A, B, C] . 您已经告诉编译器, list是类型为[A, B, C]的三元素元组 Now there are no errors (and it can see that z is a C ). 现在没有错误(并且可以看到zC )。 This is safe, with zero run-time impact. 这是安全的,对运行时的影响为零。 It also prevents you from messing with list : 它还可以防止您弄乱list

list[1] = a; // error!  A is not assignable to B

since you've told it list[1] is always a B , then you must assign something compatible with B to it: 因为您已经告诉它list[1]始终是B ,所以必须为其分配与B兼容的内容:

list[1] = c; // okay, C is assignable to B

So there are three options for you: assertions, type guards, and tuples, with tuples being the one I'd recommend for this situation. 因此,为您提供三个选项:断言,类型保护和元组,对于这种情况,我建议使用元组。 Hope that helps. 希望能有所帮助。 Good luck! 祝好运!

Have you tried using a type assertion eg. 您是否尝试过使用类型断言,例如。

const z = <B>list[2]; // omitting the .two because that would never be assignable to B

?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM