来看一个经典的类型体操问题:如何实现一个 UnionToIntersection?
具体地说,这个问题有三个子问题:联结类型的分配律、逆变地位、逆变和协变。咱们在递归地解决问题的过程中,递归地给出解答。
// test case
type U = UnionToIntersection<{a: string} | {b: number}> // type U = {a: string} & {b: number};
留神,输出不能是原始类型的联结类型,因为原始类型的穿插类型是 never
。
这个时候,就要用到这两个类型与函数的微妙碰撞了。
联结类型的分配律
咱们晓得联结类型听从分配律。当咱们将一个联结类型如 {a: string} | {b: number}
传入一个类型 type T<U>
时,type T<{a: string} | {b: number}>
实际上等价于 type T<{a: string}> | type T<{b: number}>
。
上面这个是官网的解释。
那如果咱们把 type T<U>
写成这个样子:
type ToUnionOfFunction<T> = 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<T> = ToUnionOfFunction<T> extends (x: infer P) => any ? P : never;
咱们将ToUnionOfFunction<T>
解开后便是 (((x: { a: string}) => any) | ((x: { b: number}) => any) ) extends (x: infer P) => any ? P : never
。
在 TypeScript 的这个 PR 中有一句话:
multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.
即:在 逆变地位 的同一类型变量中的多个候选会被推断成穿插类型。
基于这个性质,咱们的 UnionToIntersection<T>
便满足测试用例了。
逆变地位到底是个什么?
首先记住一句话:函数参数是逆变的,而对象属性是协变的。
变量处于逆变地位就是 这个变量是一个函数的参数。
到底什么是逆变和协变?!
在《深刻了解 TypeScript》的 逆变和协变 一节中有具体介绍。
《深刻了解 TypeScript》是本好书呀,倡议多看看。
OK,这三个问题解决完之后,咱们对这个经典问题也算是搞懂了。
本文首发于我的博客:https://callanbi.top/post/dan… 转载请注明出处。