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
发现问题
当时脑子一动,就想着是否可以把括号那部分拆分出来:
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
,简单的说就是当符合以下条件:
- 类型包含泛型
- 类型内部包含条件判断
- 传入联合类型
传入的联合类型不再是一个整体,而是拆开成独立的类型去遍历再联合起来。
ts 内置的工具类型 Exclude
和 Extract
就属于该规则的应用。
解决问题
上面只有一个例子可能不太看得明白什么是,来个对比案例看看:
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)[]
从上面的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>
的条件始终成立,所以T1
是false
参考
https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
https://zhuanlan.zhihu.com/p/469912347
评论