我目前正在学习 TypeScript 中的交集类型,我注意到当属性类型存在冲突时,使用不同类型可能会影响类型结果。
interface A {
name: string;
age: number;
}
interface B {
gender: string;
age: boolean;
}
type D = A & B; // D: never
interface A {
name: string;
age: number;
}
interface B {
gender: string;
age: string;
}
type D = A & B; // D: A & B
在第一个代码块中,D 的类型计算为
never
。
但是,在第二个代码块中,D 的类型计算为
A & B
。
//The A & B type
{
name: string;
gender: string;
age: never;
};
我个人认为推断
A & B
是一个合理且符合预期的结果,但我仍然无法理解为什么会有两个不同的结果。
TypeScript 版本 4.9.5
never
值属性的对象类型减少为never
本身。这可能是最好的,因为这样的对象类型确实出现在一些使用类型操作函数(如映射类型)的现实世界代码中,而且还因为它是可能具有这些类型的值,即使它们有点做作:
interface Foo {
x: string;
y: number;
z: never;
}
const foo: Foo = {
x: "abc",
y: 123,
get z(): never { throw new Error("GOTCHA!"); }
}
console.log(foo.x.toUpperCase()); // "ABC"
console.log(foo.y.toFixed(2)); // "123.00"
上面的一点是,如果您实际上不尝试与该属性进行交互,则正确评估
never
值是无害的。如果编译器主动尝试强制执行这一点,它将对类型系统产生明显的影响,并破坏许多现实世界的代码。 (如果你走向逻辑极端,那么任何具有抛出方法的对象也应该减少到never
)。
因此,从表面上看,这解释了为什么以下交集不会减少:
interface A1 {
name: string;
age: number;
}
interface B1 {
gender: string;
age: string;
}
type D1 = A1 & B1; // D1: A1 & B1
因为
D1
相当于 {name: string; age: never; gender: string;}
。
但是等等,这不也意味着吗
interface A2 {
name: string;
age: number;
}
interface B2 {
gender: string;
age: boolean;
}
type D2 = A2 & B2; // D2: never
不应该减少为
never
吗?嗯,这是不同的,因为boolean
类型实际上是两个布尔文字类型true | false
的并集的简写。事实上,无论您在哪里使用
true | false
,编译器都会将该类型显示为 boolean
:
type Bool = true | false;
// type Bool = boolean
因此,
age
的B2
属性可以用作可区分联合的判别式属性(判别式通常是单一文字类型,但编译器也允许您使用其他类型)它包含一些文字类型)。显然,当您编写 B2
类型时,这不是您的意图(尽管您 did 通过布尔年龄的意图尚不清楚🙃),但这就是语言的工作方式。
TypeScript 3.9 引入了对“通过判别属性减少交集”的支持。因此,如果将编译器视为判别式联合的对象类型与另一个对象类型相交,并且生成的判别属性类型为 never
,则编译器将积极地将交集减少为
never
。这是在 microsoft/TypeScript#36696中实现的,负责将
D2
缩减为 never
的行为; boolean
值的 age
属性是一个判别式,当你将它与 never
相交时,它会变成 number
,因此整个对象类型会简化为 never
。当然,如果相关属性在访问时抛出异常,则确实可以创建未简化类型的值,并且如果您不检查 never
值属性,那么这样做是安全的。但由于人们专门使用受歧视联合体来检查歧视性财产,因此这不是一个被认为需要支持的重要用例。
所以,一切都按照设计和预期运行。如果这里存在诸如错误或设计限制之类的问题,那就是用于确定对象类型是否是可区分联合成员的启发式方法不完善。您从来没有打算让B2
成为受歧视的联合成员,因此当编译器突然在与表面上相似的
B1
的交集中以不同的方式对待它时,这是令人惊讶的。Playground 代码链接