Skip to content

分发逆变推断

根据函数的型变,可以做出一些比较复杂的类型体操变化

实现高级工具类型函数:联合类型转为交叉类型

typescript
type I = UnionToIntersection<{ id: 1 } | { name: 'jack' } | { sex: '男' }>;
// { id: 1 } & { name: "jack" } & { sex: "男" }

在所有类型转换中,联合转交叉可以说是比较有难度的了

核心在于其他类型都有比较简单的遍历方法,比如元组的 T extends [infer F, ...infer R],对象的 [P in keyof T]: T[P],还有字符串的遍历套路,在这些类型中,转交叉其实非常简单。这里以元组为例:

typescript
type TupleToIntersection<T extends any[]> =
  // 递归复用遍历
  T extends [infer F, ...infer R]
    ? // 元素交叉即可
      F & TupleToIntersection<R>
    : unknown; // any & unknown = any 所以当 T 为空时,返回 unknown不影响结果

// MyType = {id: 1} & {name: 'jack'}
type MyType = TupleToIntersection<[{ id: 1 }, { name: 'jack' }]>;

但是对联合类型就麻烦了,因为我们无法把联合类型一个一个拉出来进行遍历,联合类型只有分布式(分发)特性。但是分发特性也是从一个联合类型返回一个新的联合类型,并不能转成交叉类型。

那么这个题,可以通过利用联合类型的分布式特性 + 逆变特性 + infer类型推断实现这个效果

typescript
type UnionToIntersection<U> =
  // 利用分发特性生成
  // (arg: { id: 1 }) => any |
  // (arg: { name: "jack" }) => any |
  // (arg: { sex: "男" }) => any
  (U extends any ? (arg: U) => any : never) extends (arg: infer P) => any
    ? P // 利用逆变特性,P = { id: 1 } & { name: "jack" } & { sex: "男" }
    : never;

函数参数逆变的特性不知道大家有没有忘记:

typescript
type C = { id: 1; name: 'jack'; sex: '男' } extends { id: 1 } ? 1 : 2; // 1

type D = ((arg: { id: 1 }) => any) extends (arg: { id: 1; name: 'jack'; sex: '男' }) => any ? 1 : 2; //1