TS经典类型体操(联合类型如何转为转交叉类型(需要知道三个点:分配律、逆变位置、逆变和协变))

来看一个经典的类型体操问题:如何实现一个 UnionToIntersection?
具体地说,这个问题有三个子问题:联合类型的分配律、 逆变位置、逆变和协变。我们在递归地解决问题的过程中,递归地给出解答。

// test case type U = UnionToIntersection<{ a: string } | { b: number }> // type U = {a: string } & { b: number };

注意,输入不能是原始类型的联合类型,因为原始类型的交叉类型是 never
这个时候,就要用到这两个类型与函数的奇妙碰撞了。
联合类型的分配律 我们知道联合类型遵从分配律。当我们将一个联合类型如 { a: string } | { b: number } 传入一个类型 type T 时,type T<{ a: string } | { b: number }> 实际上等价于 type T<{ a: string }> | type T<{ b: number }>
下面这个是官网的解释。
TS经典类型体操(联合类型如何转为转交叉类型(需要知道三个点:分配律、逆变位置、逆变和协变))
文章图片

那如果我们把 type T 写成这个样子:
type ToUnionOfFunction = T extends any ? (x: T) => any : never;

即,我们构造一个将传入的联合类型作为参数的函数。我们将上面的 test case 传入这个类型:
type Functions = ToUnionOfFunction<{ a: string } | { b: number }>

这个时候,结果就变成了:
type Functions = | ((x: { a: string }) => any) | ((x: { b: number }) => any)

由于分配律,我们得到了两个参数不同的函数的联合类型。
这个时候我们怎么得到交叉类型呢?
锵锵!看下面!
type UnionToIntersection =ToUnionOfFunction extends (x: infer P) => any ? P : never;

我们将ToUnionOfFunction 解开后便是 (( (x: { a: string }) => any) | ( (x: { b: number }) => any) ) extends (x: infer P) => any ? P : never
在 TypeScript 的这个 PR 中有一句话:
TS经典类型体操(联合类型如何转为转交叉类型(需要知道三个点:分配律、逆变位置、逆变和协变))
文章图片

multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.
即:在逆变位置的同一类型变量中的多个候选会被推断成交叉类型。
基于这个性质,我们的UnionToIntersection便满足测试用例了。
逆变位置到底是个什么? 首先记住一句话:函数参数是逆变的,而对象属性是协变的。
变量处于逆变位置就是这个变量是一个函数的参数。
到底什么是逆变和协变?! 在《深入理解 TypeScript》 的 逆变和协变 一节中有详细介绍。
《深入理解 TypeScript》是本好书呀,建议多看看。
【TS经典类型体操(联合类型如何转为转交叉类型(需要知道三个点:分配律、逆变位置、逆变和协变))】OK,这三个问题解决完之后,我们对这个经典问题也算是搞懂了。
本文首发于我的博客:https://callanbi.top/post/dan... 转载请注明出处。

    推荐阅读