我的问题可以用这个小片段来总结(这里是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类型),我如何才能使其使嵌套对象中的未知键被视为无效键?

最佳答案

这是一个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的类型。然后尝试对交集(cc)进行评价。如果T & Exactly<XXY, T>可分配给t,则检查成功,函数可调用。否则,T & Exactly<XXY, T>上的某个地方会出现错误,显示它们的不同之处。
并且t基本上递归地通过Exactly<T, U extends T>向下移动,保持它与匹配U的长度相同。否则它会将属性设置为T
让我们在上面的例子中对这个差异进行扩展:
{ x: { x: number; notValid: number; }; y: number; }

是否可分配给never?那么,什么是T?原来是
{ x: { x: number; notValid: never; }; y: number; }

这就是当你深入到T & Exactly<XXY, T>类型并注意到Exactly<XXY, T>中找不到T时会发生的情况。
交叉点notValid本质上只是XXY,所以现在编译器将传入的参数与类型进行比较。
{ x: { x: number; notValid: never; }; y: number; }

不是的,T & Exactly<XXY, T>类型是aExactly<XXY, T>,而不是anotValid。所以编译器会在你想要的地方抱怨。
好吧,希望能帮上忙。祝你好运!

09-16 04:35