Typescript 类型的并集和交集

Typescript 类型的并集和交集,typescript,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: fa

为了练习,我编写了以下代码:

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" };
但是
TypeScript
pet2上出现编译器错误,说我需要向我的对象添加
purrs

但是,
CatAndDog
不是这两种类型的交叉点,所以只是一个名称属性吗

交集和并集是向后的,这在人们学习打字脚本时并不少见。造成这种混淆的一个可能原因是对象类型位于其键的类型中,因此对象类型的交集具有其键的并集,反之亦然。也就是说,
keyof(猫和狗)
(keyof猫)|(keyof狗)
相同,
keyof(猫和狗)
(keyof猫)和(keyof狗)
相同:

由于这种矛盾,如果对象类型的概念与其声明的属性集相等,则会混淆交点和并集。相反,您应该将类型视为一组允许的值(有关更多信息,请参阅)。您应该将类型的并集和交集视为可分配给这些类型的值集的并集和交集

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

--

另一个可能引起混淆的原因是TypeScript中的对象类型是开放的,而不是封闭的或精确的(有关精确类型的请求,请参阅)

对象类型定义显示了哪些属性必须存在,但它们没有说明哪些属性不能存在。对象的属性可能多于其类型定义中提到的属性:

interface Garfield extends Cat {
  hatesMondays: true,
  eatsLasagna: true,
}
declare const garfield: Garfield;
const garfieldAsACat: Cat = garfield; // okay
(由于存在,这有点复杂,它将“新的”对象文本视为精确的类型。但这种检查是例外,而不是规则。)

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


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

如果我有一个
Cat&Dog
类型的对象,那么它必须同时可分配给
Cat
Dog
。因为对象类型是开放的,所以没有任何东西表明
Cat
不能具有
barks
bites
属性。也没有任何东西说一只狗不能有呼噜呼噜的特性。因此,如果您有同时是猫和狗的东西,那么它必须具有这两种类型的所有属性

pet2
失败,因为它既不是
Cat
也不是
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"
}
let pet2: CatAndDog = { name: "Timmy" }; // neither Cat nor Dog
另一方面,
Cat | Dog
类型的对象只能分配给
Cat
Dog
。如果将值分配给
Cat | Dog
类型的变量,则该变量必须至少为以下变量之一:

let okay1: CatOrDogOrBoth = 
  { name: "Sylvester", purrs: false }; // Cat
let okay2: CatOrDogOrBoth = 
  { name: "Odie", barks: true, bites: false }; // Dog
您的
pet1
是可以接受的,因为它是
Cat
。它有一个额外的
bites
属性,这很好(并且不会被多余的属性检查所捕获,尽管有些人认为它应该(参见):

如果我有一个类型为
Cat | Dog
的对象,但我还没有检查它,以确定它是
Cat
还是
Dog
中的哪一个,那么我唯一可以访问的安全属性就是它的
名称,因为这是我知道肯定会存在的唯一属性。
Cat | Dog
可能会有一些适当的属性这两种类型的关系,正如您对
pet1
的初始化所示,但您不能保证这一点



你有向后的交集和并集,这在人们学习TypeScript时并不少见。造成这种混乱的一个可能原因是对象类型在其键的类型中,因此对象类型的交集有其键的并集,反之亦然。也就是说,
keyof(Cat&Dog)
(猫的钥匙)|(狗的钥匙)
相同,
keyof(猫的狗)
(猫的钥匙)&(狗的钥匙)
相同:

由于这种矛盾,如果对象类型的概念与其声明的属性集相等,则会混淆交集和并集。相反,应将类型视为一组允许的值(有关详细信息,请参阅)。您应该将类型的并集和交集视为可分配给这些类型的值集的并集和交集

对于类似的
“foo”
,集合只有一个元素:{
“foo”
}。对于类似
字符串的类型,它是(实际上)所有JavaScript字符串的无限集合:{
x
|
typeof x==“string”
}(使用)。对于
{y:0}
等对象类型,它也是无限集合:{
x
|
x.y==0
}…也就是说,
y
属性完全等于
0
的所有JavaScript对象的集合

--

另一个可能引起混淆的原因是TypeScript中的对象类型是开放的,而不是封闭的或精确的(有关精确类型的请求,请参阅)

对象类型定义显示了哪些属性必须存在,但它们没有说明哪些属性不能存在。对象的属性可能比其类型定义中提到的多:

interface Garfield extends Cat {
  hatesMondays: true,
  eatsLasagna: true,
}
declare const garfield: Garfield;
const garfieldAsACat: Cat = garfield; // okay
(由于存在,这有点复杂,它将“新鲜”对象文本视为
let pet1: CatOrDogOrBoth = 
  { name: "pooky", purrs: true, bites: false }; // Cat with bites:false