为了练习,我写了这段代码:
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属性吗?purrs
CatAndDog
你有向后的交集和联合,出于某种原因,当人们学习 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
不能有 abarks
或bites
属性。并没有说 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
并且我还没有检查它以查看它是哪个Cat
或Dog
它是哪个,我可以访问的唯一安全属性是 its name
,因为这是我确定会出现的唯一属性。这是可能的,一个Cat | Dog
将具有两种类型的某些属性,当你表现出你的初始化pet1
,但你不能保证它。
归档时间: |
|
查看次数: |
74 次 |
最近记录: |