TypeScript中联合类型的类型推断

时间:2018-10-22 13:04:38

标签: typescript oop typing

说我有以下三个基类或类型:

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

现在,我想创建一个可以容纳这些类型的任何实例的类型化数组,因此我声明一个 Union Type ,该类型将用于该数组:

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

我也尝试过使用classinterface的定义来查看它是否有所作为-即使我确定这不是由于TypeScript's Structural Type System而引起的,并且没有任何改变。

您知道我在这里做错了什么吗,还是对更好的方法有任何建议?

答案

结果证明,将const z: B = list[2]更改为const z = <B>list[2]可以正确执行我打算进行的投射。

2 个答案:

答案 0 :(得分:2)

这不是在覆盖您的显式类型,而是通过将list视为类型为X的元素数组来服从X可以是ABC。如果我给您一个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'.

那么,您有什么选择?一种是通过使用type assertion来告诉编译器,您比其他人知道的多。

(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])也将保持安全。


更好的解决方案是使用type guards来使编译器确信您所做的工作是安全的。这具有运行时影响,因为您正在运行更多的代码,但是如果在行中的某个地方更改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
}

因此,通过使用z.two在使用in属性之前检查two属性的存在,可以保护对z的读取。现在,这是安全的。如果您正在考虑“当我知道B的类型为C(实际上是list[2],哈哈,list third 元素)”,然后继续阅读:


如果您确定A始终是类型为BCconst list: [A, B, C] = [a, b, c]; const z = list[2]; z.two; // okay z.three; // okay 的三元素数组,那么您可以知道编译器就可以在没有任何运行时防护的情况下获得所需的编译时行为。您正在寻找tuple types

list

您已经告诉编译器[A, B, C]z类型的三元素 tuple 。现在没有错误了(可以看到Clist)。这是安全的,对运行时的影响为零。它还可以防止您弄乱list[1] = a; // error! A is not assignable to B

list[1]

由于您已经告知B始终是B,因此必须为其分配与list[1] = c; // okay, C is assignable to B 兼容的内容:

SELECT id From User WHERE Username = 'test@testmail.com'

因此,为您提供三个选项:断言,类型保护和元组,对于这种情况,我建议使用元组。希望能有所帮助。祝你好运!

答案 1 :(得分:1)

您是否尝试过使用类型断言,例如。

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