Typescript 的分发条件类型(Distributive Conditional Types)踩坑记

背景

今天给我的ts 类型体操库添加一个判断类型是否是联合(Union)类型的类型体操。

具体是这样一个类型:

type IsUnion<T, U extends T = T> = (T extends any ? (U extends T ? false : true) : never) extends false ? false : true;

测试后是没问题的:

type T = IsUnion<1 | 2>; // true
type T2 = IsUnion<any>; // false
type T3 = IsUnion<void>; // false
type T4 = IsUnion<{} & {}>; // false
type T5 = IsUnion<unknown>; // false

playground

发现问题

当时脑子一动,就想着是否可以把括号那部分拆分出来:

type A<T, U extends T = T> = U extends T ? false : true;

当时我认为剔除边缘条件后,主条件使用后结果应该是相同的,
不过可惜该例子就不是,当我使用同样的测试用例:

type T = IsUnion<1 | 2>; // true
type T1 = A<1 | 2>;  // false

可以发现同样的用例测试结果是不一样的。

查找解决方案

上面的两种结果让我一时想不明白,所以我通过微信群询问群友,也向chatGPT询问了:

可惜chatGPT似乎不太擅长typescript的类型体操,这里的T的类型就明显不对了。

好在万能的群友说指出这是 distributive conditional types。

通过官方文档发现有这样一个例子:

通过反复观摩这个例子,我终于悟了。

什么是distributive-conditional-types,简单的说就是当符合以下条件:

  1. 类型包含泛型
  2. 类型内部包含条件判断
  3. 传入联合类型

传入的联合类型不再是一个整体,而是拆开成独立的类型去遍历再联合起来。

ts 内置的工具类型 ExcludeExtract 就属于该规则的应用。

解决问题

上面只有一个例子可能不太看得明白什么是,来个对比案例看看:

type Dist<T, U extends T = T> = T[];
type Dist2<T, U extends T = T> = U[];
type Dist3<T, U extends T = T> = T extends any ? T[] : never;
type Dist4<T, U extends unknown = T> = T extends any ? U[] : never;

type D = Dist<1 | 2>; // (1 | 2)[]
type D2 = Dist2<1 | 2>; // (1 | 2)[]
type D3 = Dist3<1 | 2> // 1[] | 2[]
type D4 = Dist4<1 | 2> // (1 | 2)[]

playground

从上面的D3的类型可以看出未分发的类型是(1 | 2)[]这样的,而经过分发的类型是1[] | 2[]这样的;

所以可以把IsUnion<1 | 2>的步骤分解为:

// IsUnion<1 | 2> 分发后可分解为
type Step1 = 1 | 2 extends 1 ? false : true; // true
type Step2 = 1 | 2 extends 2 ? false : true; // true
type Res = Step1 | Step2; // true

type A<T, U extends T = T> = U extends T ? false : true;

type T1 = A<1 | 2>;  // false

A<1 | 2>的条件始终成立,所以T1false

参考

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
https://zhuanlan.zhihu.com/p/469912347

评论

0 / 800
全部评论()