分布式条件类型
分布式条件类型也称条件类型的分布式特性,只不过是条件类型在满足一定情况下会执行的逻辑而已。
type Condition<T> = T extends 1 | 2 | 3 ? T : never;
// 1 | 2 | 3
type Res1 = Condition<1 | 2 | 3 | 4 | 5>;
// never
type Res2 = 1 | 2 | 3 | 4 | 5 extends 1 | 2 | 3 ? 1 | 2 | 3 | 4 | 5 : never;
仔细观察这两个类型别名的差异会发现,唯一的差异就是在 Res1 中,进行判断的联合类型被作为泛型参数传入给另一个独立的类型别名,而 Res2 中直接对这两者进行判断。
记住第一个差异:是否通过泛型参数传入。
type Naked<T> = T extends boolean ? 'Y' : 'N';
type Wrapped<T> = [T] extends [boolean] ? 'Y' : 'N';
// "N" | "Y"
type Res3 = Naked<number | boolean>;
// "N"
type Res4 = Wrapped<number | boolean>;
现在都是通过泛型参数传入了,但诡异的事情又发生了,为什么第一个还是个联合类型?
第二个倒是好理解一些,元组的成员有可能是数字类型,显然不兼容于 [boolean]。
再仔细观察这两个例子会发现,它们唯一的差异是条件类型中的泛型参数是否被数组包裹了。
同时你会发现在 Res3 的判断中,其联合类型的两个分支,恰好对应于分别使用 number 和 boolean 去作为条件类型判断时的结果。
把上面的线索理一下大致得到了条件类型分布式起作用的条件:
- 类型参数需要是一个联合类型。
- 类型参数需要通过泛型参数的方式传入。
- 条件类型中的泛型参数不能被包裹。
条件类型分布式特性会产生的效果也很明显了,即将这个联合类型拆开来,每个分支分别进行一次条件类型判断,再将最后的结果合并起来(如 Naked 中)。
官方的解释:对于属于裸类型参数的检查类型,条件类型会在实例化时期自动分发到联合类型上。
这里的自动分发可以这么理解:
type Naked<T> = T extends boolean ? 'Y' : 'N';
// (number extends boolean ? "Y" : "N") | (boolean extends boolean ? "Y" : "N")
// "N" | "Y"
type Res3 = Naked<number | boolean>;
这里的裸类型参数,其实指的就是泛型参数是否完全裸露,上面使用数组包裹泛型参数只是其中一种方式,比如还可以这么做:
export type NoDistribute<T> = T & {};
type Wrapped<T> = NoDistribute<T> extends boolean ? "Y" : "N";
type Res1 = Wrapped<number | boolean>; // "N"
type Res2 = Wrapped<true | false>; // "Y"
type Res3 = Wrapped<true | false | 18>; // "N"
需要注意的是,并不是只会通过裸露泛型参数,来确保分布式特性能够发生。
在某些情况下也会需要包裹泛型参数来禁用掉分布式特性。最常见的场景也许还是联合类型的判断,即不希望进行联合类型成员的分布判断,而是希望直接判断这两个联合类型的兼容性判断。
就像在最初的 Res2 中那样:
type CompareUnion<T, U> = [T] extends [U] ? true : false;
type CompareRes1 = CompareUnion<1 | 2, 1 | 2 | 3>; // true
type CompareRes2 = CompareUnion<1 | 2, 1>; // false
通过将参数与条件都包裹起来的方式对联合类型的比较就变成了数组成员类型的比较,在此时就会严格遵守类型层级一文中联合类型的类型判断。
另外一种情况则是,当想判断一个类型是否为 never 时,也可以通过类似的手段:
type IsNever<T> = [T] extends [never] ? true : false;
type IsNeverRes1 = IsNever<never>; // true
type IsNeverRes2 = IsNever<'wangxiaobai'>; // false
这里的原因其实并不是因为分布式条件类型。当条件类型的判断参数为 any,会直接返回条件类型两个结果的联合类型。
而在这里其实类似,当通过泛型传入的参数为 never,则会直接返回 never。
需要注意的是这里的 never 与 any 的情况并不完全相同,any 在直接作为判断参数时、作为泛型参数时都会产生这一效果:
// 直接使用,返回联合类型
type Tmp1 = any extends string ? 1 : 2; // 1 | 2
type Tmp2<T> = T extends string ? 1 : 2;
// 通过泛型参数传入,同样返回联合类型
type Tmp2Res = Tmp2<any>; // 1 | 2
// 如果判断条件是 any,那么仍然会进行判断
type Special1 = any extends any ? 1 : 2; // 1
type Special2<T> = T extends any ? 1 : 2;
type Special2Res = Special2<any>; // 1
而 never 仅在作为泛型参数时才会产生:
// 直接使用,仍然会进行判断
type Tmp3 = never extends string ? 1 : 2; // 1
type Tmp4<T> = T extends string ? 1 : 2;
// 通过泛型参数传入,会跳过判断
type Tmp4Res = Tmp4<never>; // never
// 如果判断条件是 never,还是仅在作为泛型参数时才跳过判断
type Special3 = never extends never ? 1 : 2; // 1
type Special4<T> = T extends never ? 1 : 2;
type Special4Res = Special4<never>; // never
这里的 any、never 两种情况都不会实际地执行条件类型,而在这里通过包裹的方式让它不再是 never,也就能够去执行判断了。
项目地址: TSHandbook
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论