联合类型(Union Types)与交叉类型(Intersection Types)在 TS 中都属于很基础的概念,我们也知道联合类型是取并集,用符合(|
),交叉类型是取交集,用符号(&
)。<br />但是实际效果和我们直观认知是不一致的,比如如下的代码,有一个 Demo1
类型和 Demo2
类型,Demo3
类型是 Demo1
和 Demo2
的联合类型。
type Demo1 = {
a: string;
b: string;
};
type Demo2 = {
b: string;
c: string;
};
type Demo3 = Demo1 | Demo2;
const demo3: Demo3 = {} as any;
demo3.a; // ERROR
按照我们对并集的理解,demo3
的属性应该是 demo1
和 demo2
的并集。所以 demo3
里应该有 a, b, c
。<br />但是我们实际敲一下代码试一试,却发现不是这样的,我们只能使用 demo3.b
这一个属性。<br />相反,如果我们使用的是交叉类型,却可以访问所有的成员:
type Demo4 = Demo1 & Demo2;
const demo4: Demo4 = {} as any;
const { a, b, c } = demo4;
为什么会和我们直观感受正好相反呢?其实我们要弄清楚,TS 中的取交集和取并集究竟是对什么来说的。其实 TS 中所有对类型的计算,都是针对使用这个类型的对象来说的。<br />拿上面那个联合类型的例子来讲,取并集是对所有为 Demo1
和 Demo2
的对象取并集。这样的结果就是这个集合里即可能有 Demo1
(有 a
和 b
),又可能有 Demo2
(有 b
和 c
)。而上一讲我们讲过,TS 要保证的是所有成员始终可用,那么也就意味着我们只能用 b
。<br />我们可以举个实际的例子来看得更清楚一点,比如我们定义两个类型,一个表示蔡徐坤的粉丝,一个表示马保国的粉丝。
- 蔡粉
- 鬼畜
- 唱
- 跳
- rap
- 篮球
- 马粉
- 鬼畜
- 五连鞭
- 太极
- 讲武德
下面我们说:菜粉的举左手,马粉的举右手。然后刚才举过手的站出来(联合类型)。这个时候站出来的肯定既有蔡粉又有马粉。这个时候我们能说站出来的打个太极吗?肯定是不行的,因为蔡粉不会打太极。我们只能说站出来的来段鬼畜,因为蔡粉和马粉都会鬼畜。<br />相反的,如果我们说,菜粉的举左手,马粉的举右手。然后两只手都举着的站出来(交叉类型)。这个时候站出来的肯定既是蔡粉也是马粉。如果我们说站出来的打个太极,肯定是没问题的,因为他们肯定是马粉,肯定会打太极。如果我说站出来的来段 rap,也是没问题的,因为他们肯定是蔡粉,肯定会 rap。<br />所以其实知识并不复杂,我们只要能够理解这一点的话,就不用再去记什么乱七八糟的规则了,所有的现象都可以解释了,比如我们随便来写一个例子:
type Test1 = 'a' & string
这是两个原始类型,得到的 Test1
的结果就是 a
,我们还拿坤坤来举例,坤坤的粉丝举右手(string
),坤坤的超级粉丝举左手(a
)。然后两只手都举起来的站起来。这个时候两只手都举起来的,肯定是坤坤的超级粉丝。所以这里 a
是字符串的一种特殊情况,所以 Test1
的结果就是 a
。<br />再看一个例子
type Test2 = 'a' & 'b'
最终 Test2
的结果是 never
,不存在。因为它不可能同时是 a
又同时是 b
。就好比说坤坤的粉丝举左手,坤坤的黑子举右手。然后两只手都举起来的站起来,那么会有人站起来吗,肯定不会。所以是 never
。<br />再看一个例子
type Test3 = 'a' | 'b' | 1 & string
这边就涉及到运算了,当联合类型跟其他进行运算时,是分开进行运算的,上面的例子其实等效于:
type Test3 = ('a' & string) | ('b' & string) | (1 & string)
我们知道 'a' & stirng
最终的结果是 'a'
,'b' & string
的结果是 'b'
,1 & string
最终的结果是 never
。<br />所以 Test3
最终的结果是 'a' | 'b'
。<br />最后我们看一个复杂的例子,大家可以试着能否根据本期所学的知识,结合上期的协变与逆变,来读懂这个类型函数的作用。
type UnionToIntersection<T> =
(T extends any ? (x: T) => any : never) extends
(x: infer R) => any ? R : never