交叉类型(Intersection Types)(交集)

1
type A = string & number

很显然 A 的结果是 never,因为字符串和数字是两种完全不同的东西,所以一般交叉类型,我们不会用在普通类上

1
2
3
4
5
6
7
8
type 有左手的人 = {
left: string;
}
type 有右手的人 = {
right: string;
}
type C = 有左手的人 | 有右手的人
type D = 有左手的人 & 有右手的人

以上的 C、D 类型用一张图表示:

思考”有左手的人” 可以有”右手”吗?

1
2
3
4
5
6
7
8
type 有左手的人 = {
left: string;
}

const a: 有左手的人 = {
left: '左手',
right: '右手', // TODO: 这里会有报错
}

很显然,这里的代码报错了,但是从逻辑上来讲,”有左手的人” 难道不能有”右手”吗?,再来看接下来的栗子:

1
2
3
4
5
6
7
8
9
10
type 有左手的人 = {
left: string;
}

const b = {
left: '左手',
right: '右手',
}

const a: 有左手的人 = b

神奇的是,竟然不报错了,这就是 ts 很奇怪的地方,初始化的时候不能有多余的东西

接口也能交集

1
2
3
4
5
6
7
8
interface 有左手的人 {
left: string;
}
interface 有右手的人 {
right: string;
}

type 完整的手 = 有左手的人 & 有右手的人

特殊情况

普通对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Person = {
name: string;
age: number;
id: string;
}
type User = Person & {
id : number; // TODO: 注意:这里并没有报错
email: string;
}

const a: User = {
id: 1, // TODO: 这里报错了,并且提示类型为 never,
name: 'Jack',
email: 'qq.com',
}

但是稍微改造一点,还有更特殊的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

type Person = {
name: string;
age: number;
id: 'A'; // TODO: 这里更具体了
}
type User = Person & {
id : 'B'; // TODO: 这里更具体了
email: string;
}

const a: User = { // TODO: 这里直接提示 User 类型为 never,
id: 1,
name: 'Jack',
email: 'qq.com',
}

这就很难解释了,直接记住这种情况把~

函数

1
2
3
4
5
6
7
8
9
10
11
12
type A = {
method: (n: number) => void
}
type B = {
method: (n: string) => void
}

const b: B = {
method: n => { // TODO: 没有报错
console.log(n)
}
}

可以发现当为函数的时候这里没有报错,且参数 n 的类型为 string | number,

一个接受参数类型为 number 的方法,一个接受参数类型为 string 的方法,这两个方法有没有交集呢?到目前来看已经很难想清楚了,明明是交集,怎么会得出并集!我们再来验证一下:

1
2
3
4
5
6
7
8
type F1 = (n: number) => void
type F2 = (n: string) => void

type X = F1 & F2

const x: X = (n) => {
console.log(n)
}

也没有报错,且参数 n 的类型为 string | number

也就是说,如果我们再在做两个对象交集的时候,遇到 key 的名字冲突的时候,那么他会对这个属性进行交集,且递归的交。

但是特殊在函数这里,函数的交集,会得到参数的并集,说实话我又不是很理解了,没办法按实践的结果来死记硬背把~