TypeScript Conditional Types 条件类型在设计上是 distributable 的, 也就是可以按分配律,也可以不按分配律做处理,具体来说,只有在有泛型参数出现的情况下才会按 distributive 做处理,理由是底层实现如此:
而什么是分配律?其实跟乘法的分配律一样,是代数系统一种概念, 如:
type ABABC =
// 下面这个计算, 其实 TS 内部将会根据分配律进行处理
'A' | 'B' extends 'A' | 'B' | 'C' ? 1 : 2; // got 1
// 其中过程需要将左边的 'A' | 'B' 拆开跟右边的 A B C 单独做 extends 最后将结果用 | 链接起来作为 union, 即分配律:
type ABABC_2 = (
('A' extends 'A' | 'B' | 'C' ? 1 : 2) |
('B' extends 'A' | 'B' | 'C' ? 1 : 2)
); // got 1有人说, never 是空集, 可是这个 集 到底是什么 ? 要理解 never 我们仅需在 TS 中自己实现一个判断 IsNever 就能理解了,而我写的 IsNever 表现上很奇怪:
type A1 = never extends never ? 1 : 2; // got 1 never 是 never 这没有问题
type B1 = 11111 extends never ? 1 : 2; // got 2 11111 不是 never 所以是 2 也没有问题
// 观察到上面的 A1 B1 可以发现用 T extends never 实现, 从而将这种模式提取为泛型:
type IsNever<T> = T extends never ? 1 : 2;
// 然后我们试试
type A2 = IsNever<never>; // 然而 A2 居然是 never, 并不是预期中的 1, 甚至不是 conditional types 的左项和右项的其中一个为什么呢?因为 TS 底层实现的 never 其实是成员数量为 0 的 union 类型,于是根据前文的分配律,IsNever<T> 里面 T extends never 需要遵守分配律, 此时如果 T 是 never, 那就啥东西都配不出来了, 最后就得到了 never —— 一个长度为 0 的 union 类型
核心点在于我们需要避免 IsNever<T> 之 T 是一个 union 类型,防止它变成 distributive 的条件类型,避免前面的情况。参考了其他人的写法, 可以得到:
type IsNever<T> = [T] extends [never] ? 1 : 2;
type CCCC = IsNever<never>; // got 1
type DDDD = IsNever<11111>; // got 2
Is worthy of my good teacher