首页 > 解决方案 > 为什么 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

标签: typescript

解决方案


您正在谈论的问题称为“子类型崩溃”,如GitHub 问题引入联合类型中所述。这是弃权的一般概念的一部分,我过去曾要求对此提供更多支持

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

但它不会在 quickinfo/IntelliSense 中发生,并且有一个相当令人信服的原因:过度的属性检查。TypeScript 中的对象类型通常被认为是“开放的”,因为您可以通过添加额外的属性来扩展类型。如果总是这样,那么SillyObj将完全等同于Obj

开放类型的替代方案是“封闭”或“精确”类型,其中不允许对象具有额外属性。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

这是自动错误消息难以避免的后果。如果一个值不能分配给联合,那是因为它不能分配给联合的任何组成部分。错误消息可能会提到所有成分,这将非常冗长,或者它只提到一些成分,这可能会让您相信修复错误的唯一方法是使值可分配给提到的成分. 我可能会说这里的错误是“误导”,而不是“错误”。

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


推荐阅读