为什么TypeScript不急于简化愚蠢的联合类型?

时间:2019-05-05 11:54:14

标签: typescript

这些类型实际上是相同的,但是在错误消息和语言服务帮助工具提示中的表示方式有所不同。为什么?

type Obj = { a: number }

// hovering over this shows:
// type SillyObj = { a: number; } | { a: number; b: number; }
type SillyObj = { a: number } | { a: number, b: number }

declare const obj: Obj;
declare const sillyObj: SillyObj;

// but the two types are inter-assignable!
// shouldn't SillyObj's representation be simplified
// to `{ a: number }`?
let check1: Obj = sillyObj; // OK
let check2: SillyObj = obj; // OK

// this error message is just wrong
// Type '{}' is not assignable to type 'SillyObj'.
//   Type '{}' is missing the following properties from type '{ a: number; b: number; }': a, b
let check3: SillyObj = {} // OK

1 个答案:

答案 0 :(得分:2)

您正在谈论的问题称为GitHub issue introducing union types中提到的“子类型崩溃”。它是absoption总体概念的一部分,过去我曾要求过more support for this

正如您所注意到的那样,在检查具体类型的可分配性时会发生这种崩溃的情况。也就是说,当A | (A & B)A不是通用时,编译器将类型AB视为可相互分配的。

但是它不会在quickinfo / IntelliSense中发生,并且有一个相当有说服力的原因:excess property checking。 TypeScript中的对象类型通常被认为是“开放的”,因为您可以通过添加额外的属性来扩展类型。如果情况总是如此,则SillyObj将完全等同于Obj

开放类型的替代方法是“封闭”或"exact"类型,其中不允许对象具有额外的属性。而且TypeScript将“新鲜”对象文字视为需要符合类型的封闭版本而不是正常的开放版本。突然之间,SillyObj并不被认为完全等同于Obj

const s: SillyObj = { a: 1, b: 2 }; // okay
const o: Obj = { a: 1, b: 2 }; // error!  "b" does not exist in Obj

如果SillyObj被主动吸收/折叠/减少为Obj,那么多余的属性检查将使您无法分配b属性。

现在,可能有多种方法可以使您真正的子类型折叠并维护多余的属性检查,但是它们需要对语言中确切类型的实际支持,但尚不存在。


此外,我不会认为错误消息“错误”。这确实是正确的:

// Type '{}' is not assignable to type 'SillyObj'.

从技术上讲,这部分也是正确的,但是错过了仅添加a属性会导致错误消失的细微差别:

//   Type '{}' is missing the following properties from type '{ a: number; b: number; }': a, b

这是自动错误消息难以避免的结果。如果无法将值分配给联合,则是因为无法将值分配给联合的任何组成部分。错误消息可能会提及组成部分的 all ,这是非常冗长的,或者它只提及了组成部分的 some ,这可能会使您相信唯一的方法解决错误的方法是使值可分配给上述成分。我可能会说这里的错误是“误导”,而不是“错误”。

无论如何,希望能有所帮助。祝你好运!