类型的并集和交集 [英] Union and Intersection of types
问题描述
为了练习,我写了这段代码:
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
不能有 barks
或 bites
属性.并没有说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
并且我还没有检查它以查看它是 Cat
或 Dog
中的哪一个,我可以访问的唯一安全属性是它的 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.
这篇关于类型的并集和交集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!