嵌套对象中的TypeScript额外键

时间:2019-03-26 14:51:22

标签: typescript

我的问题可以用这个小片段来总结(这是Playground中较大的交互式示例):

type X = {x: number};
type Y = {y: number};
type XXY = { x: X } & Y;
let xxy: XXY = {
    x: {
        x: 1,
        notValid: 1   // <--- this is not an error :(
    },
    y: 1
};

鉴于XY是通过另一种方式派生的(所以我不能只手工编写XXY类型),如何使它成为未知键在嵌套对象中被视为无效?

1 个答案:

答案 0 :(得分:1)

这是known bug,其中excess property checking不适用于人们期望的涉及联合和交集的嵌套类型。多余的属性检查是类型系统的一个附加组件,它仅适用于对象文字,因此当它不适用时,情况会退回到structural subtyping规则中,其中类型{a: A, b: B}是子类型{a: A}的值,因此前一种类型的值应可分配给后一种类型的变量。如果您认为此案例比已经列出的案例更具吸引力,那么您可能想要转到the issue in Github并给出一个案例或解释您的用例。希望有一天会解决此问题。

在此之前,有解决方法。等同于多余属性检查的类型级别称为exact types,它在TypeScript中不作为具体类型存在。有多种方法可以使用通用辅助函数进行模拟并进行类型推断...在您的情况下,它看起来像这样:

type Exactly<T, U extends T> = T extends object ?
  { [K in keyof U]: K extends keyof T ? Exactly<T[K], U[K]> : never }
  : T

const asXXY = <T extends XXY>(x: T & Exactly<XXY, T>): T => x;

let xxy = asXXY({
  x: {
    x: 1,
    notValid: 1  // error!
  },
  y: 1
});

那是你想要的错误,对吧?

工作原理:辅助函数asXXY<T extends XXY>(t: T & Exactly<XXY, T>)推断通用类型T为传入参数t的类型。然后,它尝试评估交点T & Exactly<XXY, T>。如果t可分配给T & Exactly<XXY, T>,则检查成功,并且该函数可调用。否则,t某处会出现错误,表明它们之间的区别。

然后Exactly<T, U extends T>基本上递归地遍历U,只要它与T匹配就保持不变...否则,将属性设置为never

在上述情况下,我们将扩展此差异:T的推断类型为

{ x: { x: number; notValid: number; }; y: number; }

可以分配给T & Exactly<XXY, T>吗?好吧,Exactly<XXY, T>是什么?原来是

{ x: { x: number; notValid: never; }; y: number; }

当您深入到类型T并注意notValid中找不到XXY时会发生这种情况。

交集T & Exactly<XXY, T>本质上只是Exactly<XXY, T>,所以现在编译器正在将传入的参数与类型进行比较

{ x: { x: number; notValid: never; }; y: number; }

不是。 notValid类型是number,而不是never ...,因此编译器会在您想要的位置抱怨。

好的,希望能有所帮助。祝你好运!