类型的并集和交集 [英] Union and Intersection of types

查看:23
本文介绍了类型的并集和交集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

type Cat = {name: string, purrs: boolean};type Dog = {name: string, barks: boolean, bits: boolean};输入 CatOrDogOrBoth = 猫 |狗;输入 CatAndDog = Cat &狗;let pet1 : CatOrDogOrBoth = {name: "pooky", purrs: true, bits: false};让 pet2 : CatAndDog = {name: "Timmy" };

但是pet2 上的TypeScript 编译器错误说我需要将purrs 添加到我的对象中.但是 CatAndDog 不是这两种类型的交集,所以只有一个 name 属性吗?

解决方案

交集和并集倒过来了,出于某种原因,当人们了解 TypeScript 时,这种情况并不少见.造成这种混淆的一个可能原因是对象类型是 contravariant 在其键的类型中,因此对象类型的交集具有其键的并集,反之亦然.也就是说,keyof(Cat & Dog)(keyof Cat) | 是一样的.(keyof Dog)keyof (Cat | Dog)(keyof Cat) & 相同(狗的钥匙):

type KeyExploration = {keysOfIntersection: keyof (Cat & Dog)//名字"|咕噜"|吠声"|咬"unionOfKeys: keyof Cat |keyof Dog//名字"|咕噜"|吠声"|咬"keysOfUnion: keyof (Cat | Dog)//"name";crossOfKeys: (keyof Cat) &(keyof Dog)//名字"}

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

对于文字类型"foo",集合有一个元素:{ "foo" }.对于像 string 这样的类型,它是所有 JavaScript 字符串的(实际上)无限集:{ x |typeof x === "string" } (使用 设置构建器符号).对于像 {y: 0} 这样的对象类型,它也是一个无限集:{ x |xy === 0 }... 即所有具有 y 属性的 JavaScript 对象的集合,它完全等于 0.>

--

另一个可能引起混淆的原因是 TypeScript 中的对象类型是开放而不是封闭精确(请参阅microsoft/TypeScript#12936 请求确切类型).

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

interface Garfield extends Cat {讨厌星期一:真的,吃千层面:真的,}声明常量加菲猫:加菲猫;const garfieldAsACat:猫 = 加菲猫;//好的

(由于 excess属性检查,将新鲜"对象字面量视为精确类型.但此类检查是例外而不是规则.)

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


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

如果我有一个 Cat & 类型的对象Dog,它必须可以分配给both Cat Dog.因为对象类型是开放的,所以没有说 Cat 不能有 barksbites 属性.并没有说Dog 不能有purrs 属性.因此,如果您拥有既是 Cat 又是 Dog 的东西,则它必须具有两种类型的所有属性.

let OK1: CatAndDog ={名称:猫狗",呼噜声:真实,咬伤:真实,吠声:真实};//下大雨

而且 pet2 失败了,因为它既不是 Cat 也不是 Dog:

let pet2: CatAndDog = { name: "Timmy";};//既不是猫也不是狗

另一方面,Cat | 类型的对象Dog 只能分配给 either Cat Dog.如果为 Cat | 类型的变量赋值狗它至少需要是其中之一:

let OK1: CatOrDogOrBoth ={名称:西尔维斯特",呼噜声:假};//猫让okay2:CatOrDogOrBoth ={ 名称:Odie",吠声:真,咬伤:假 };//狗

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

let pet1: CatOrDogOrBoth ={ name: pooky", purrs: true, bits: false };//被咬的猫:false

如果我有一个 Cat | 类型的对象Dog 并且我还没有检查它以查看它是 CatDog 中的哪一个,我可以访问的唯一安全属性是它的 name,因为这是我确定肯定会出现的唯一属性.可能Cat |Dog 将具有两种类型的一些属性,正如您在 pet1 的初始化中所展示的那样,但您不能保证这一点.


链接到代码

For practice I wrote this code:

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" };

But TypeScript compiler errors on pet2 saying I need to add purrs to my object. But isn't CatAndDog the intersection of those two types so only a name property ?

解决方案

You've got intersection and union backwards, which for some reason is not uncommon when people learn about TypeScript. One probable cause for this confusion is that an object type is contravariant in the type of its keys, so an intersection of object types has a union of its keys and vice versa. That is, keyof (Cat & Dog) is the same as (keyof Cat) | (keyof Dog), and keyof (Cat | Dog) is the same as (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"
}

Because of this contravariance, you will get intersections and unions mixed up if your conception of an object type is equivalent to its set of declared properties. Instead, you should think about a type as a set of allowed values (see this answer for more information). And you should think of unions and intersections of types as unions and intersections of the sets of values assignable to those types.

For a literal type like "foo", the set has a single element: { "foo" }. For a type like string, it's the (practically) infinite set of all JavaScript strings: { x | typeof x === "string" } (using set builder notation). And for object types like {y: 0}, it's also an infinite set: { x | x.y === 0 }... that is, the set of all JavaScript objects with a y property exactly equal to 0.

--

Another possible source of confusion is that object types in TypeScript are open and not closed or exact (see microsoft/TypeScript#12936 for a request for exact types).

Object type definitions show which properties must be present, but they do not talk about which properties must not be present. An object may have more properties than mentioned in its type's definition:

interface Garfield extends Cat {
  hatesMondays: true,
  eatsLasagna: true,
}
declare const garfield: Garfield;
const garfieldAsACat: Cat = garfield; // okay

(This is complicated a bit by the presence of excess property checks, which treat "fresh" object literals as if they were exact types. But such checks are the exception and not the rule.)

Since object types are open, it means that the set of assignable values is larger than you might have thought. Two object types like {a: 0} and {b: 1} actually have significant overlap; for example, the value {a: 0, b: 1, c: 2, d: 3} is assignable to both of them.


Now let's think about intersection (&) and union (|):

If I have an object of type Cat & Dog, it must be assignable to both Cat and Dog. Because object types are open, nothing says that a Cat cannot have a barks or a bites property. And nothing says that a Dog cannot have a purrs property. So if you have something that is both a Cat and a Dog, it must have all the properties of both types.

let okay1: CatAndDog = 
  { name: "CatDog", purrs: true, bites: true, barks: true }; // Cat and Dog

And pet2 fails because it's neither a Cat nor a Dog:

let pet2: CatAndDog = { name: "Timmy" }; // neither Cat nor Dog

On the other hand, an object of type Cat | Dog must only be assignable to either Cat or Dog. If you assign a value to a variable of type Cat | Dog it needs to be at least one of those:

let okay1: CatOrDogOrBoth = 
  { name: "Sylvester", purrs: false }; // Cat
let okay2: CatOrDogOrBoth = 
  { name: "Odie", barks: true, bites: false }; // Dog

Your pet1 is acceptable because it's a Cat. It has an extra bites property, which is fine (and is not caught by excess property checking, although some people think it should (see microsoft/TypeScript#20863):

let pet1: CatOrDogOrBoth = 
  { name: "pooky", purrs: true, bites: false }; // Cat with bites:false

If I have an object of type Cat | Dog and I haven't yet inspected it in order to see which of Cat or Dog it is, the only safe property I can access is its name, because that's the only property I know for sure will be present. It is possible that a Cat | Dog will have some properties from both types, as you show by your initialization of pet1, but you can't guarantee it.


Link to code

这篇关于类型的并集和交集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆