类型的并集和交集

Boh*_*ohn 1 typescript

为了练习,我写了这段代码:

type Cat = {name: string, purrs: boolean};
type Dog = {name: string, barks: boolean, bites: boolean};

type CatOrDogOrBoth = Cat | Dog;
type CatAndDog = Cat & Dog;

let pet1 : CatOrDogOrBoth = {name: "pooky", purrs: true, bites: false};
let pet2 : CatAndDog = {name: "Timmy" };
Run Code Online (Sandbox Code Playgroud)

但是pet2TypeScript上的编译器错误说我需要添加到我的对象中。但是这两种类型的交集不是只有name属性吗?purrsCatAndDog

jca*_*alz 5

你有向后的交集和联合,出于某种原因,当人们学习 TypeScript 时,这并不少见。造成这种混淆的一个可能原因是对象类型的键类型是逆变的,因此对象类型的交集具有其键的并集,反之亦然。也就是说,keyof (Cat & Dog)与 相同(keyof Cat) | (keyof Dog),并且keyof (Cat | Dog)与 相同(keyof Cat) & (keyof Dog)

type KeyExploration = {
  keysOfIntersection: keyof (Cat & Dog) // "name" | "purrs" | "barks" | "bites"
  unionOfKeys: keyof Cat | keyof Dog // "name" | "purrs" | "barks" | "bites"
  keysOfUnion: keyof (Cat | Dog) // "name"
  intersectionOfKeys: (keyof Cat) & (keyof Dog) // "name"
}
Run Code Online (Sandbox Code Playgroud)

由于这种逆变,如果您对对象类型的概念等同于其声明的属性集,您将混淆交集和并。相反,您应该将类​​型视为一组允许的值(有关更多信息,请参阅此答案)。并且您应该将类​​型的联合和交集视为可分配给这些类型的值集的联合和交集。

对于像 那样的文字类型"foo",集合只有一个元素:{ "foo"}。对于像 那样的类型string,它是所有 JavaScript 字符串的(实际上)无限集:{ x| typeof x === "string"} (使用集合生成器符号)。对于像{y: 0}这样的对象类型,它也是一个无限集:{ x| x.y === 0}... 也就是说,y属性完全等于的所有 JavaScript 对象的集合0

——

另一个可能的混淆来源是 TypeScript 中的对象类型是开放的,而不是封闭的精确的(请参阅microsoft/TypeScript#12936以获取精确类型的请求)。

对象类型定义显示了哪些属性必须存在,但它们并没有讨论哪些属性不能存在。一个对象可能具有比其类型定义中提到的更多的属性:

interface Garfield extends Cat {
  hatesMondays: true,
  eatsLasagna: true,
}
declare const garfield: Garfield;
const garfieldAsACat: Cat = garfield; // okay
Run Code Online (Sandbox Code Playgroud)

(由于存在多余的属性检查,这有点复杂,它将“新鲜”对象文字视为精确类型。但此类检查是例外而不是规则。)

由于对象类型是开放的,这意味着可分配值的集合比您想象的要大。两个对象类型像{a: 0}{b: 1}实际上有显着的重叠;例如,该值{a: 0, b: 1, c: 2, d: 3}可分配给它们两个。


现在让我们考虑交集 ( &) 和联合 ( |):

如果我有类型的对象Cat & Dog,它必须是分配给两个 Cat Dog。因为对象类型是开放的,所以没有说 aCat不能有 abarksbites属性。并没有说 aDog不能拥有purrs属性。所以如果你有一个既是 aCat又是 a 的东西Dog,它必须具有这两种类型的所有属性

let okay1: CatAndDog = 
  { name: "CatDog", purrs: true, bites: true, barks: true }; // Cat and Dog
Run Code Online (Sandbox Code Playgroud)

并且pet2失败,因为它既不是 aCat也不是 a Dog

let pet2: CatAndDog = { name: "Timmy" }; // neither Cat nor Dog
Run Code Online (Sandbox Code Playgroud)

在另一方面,类型的对象Cat | Dog必须仅分配给任一 Cat Dog。如果你给一个类型的变量赋值,Cat | Dog它至少需要是其中之一:

let okay1: CatOrDogOrBoth = 
  { name: "Sylvester", purrs: false }; // Cat
let okay2: CatOrDogOrBoth = 
  { name: "Odie", barks: true, bites: false }; // Dog
Run Code Online (Sandbox Code Playgroud)

pet1是可以接受的,因为它是一个Cat. 它有一个额外的bites属性,这很好(并且不会被额外的属性检查所捕获,尽管有些人认为它应该这样做(请参阅microsoft/TypeScript#20863):

let pet1: CatOrDogOrBoth = 
  { name: "pooky", purrs: true, bites: false }; // Cat with bites:false
Run Code Online (Sandbox Code Playgroud)

如果我有一个类型的对象Cat | Dog并且我还没有检查它以查看它是哪个CatDog它是哪个,我可以访问的唯一安全属性是 its name,因为这是我确定会出现的唯一属性。这是可能的,一个Cat | Dog将具有两种类型的某些属性,当你表现出你的初始化pet1,但你不能保证它。


代码链接