联合类型在类型编程中比较特殊,TypeScript 对它做了专门的处理,写法上可以简化,但也增加一些认知成本。
分布式条件类型
当类型参数为联合类型,并且在条件类型左边直接引用该类型参数的时候,TypeScript 会把每一个元素单独传入来做类型运算,最后再合并成联合类型,这个种语法叫做分布式条伯类型。
type Union = 'a' | 'b' | 'c';type str = `${Union}~~`;// type str = "a~~" | "b~~" | "c~~"
这样简化类型编程逻辑,不需要递归提取每个元素再处理。
联合类型的每个元素都是互不相关,不像数组、索引、字符串这些元素之间是有关系。设计成每一个独立处理,最后合并。
// Camelcasetype Camelcase<Str extends string> =Str extends `${infer Left}_${infer Right}${infer Rest}`? `${Left}${Uppercase<Right>}${Camelcase<Rest>}`: Str;// CamelcaseArrtype CamelcaseArr<Arr extends unknown[],Result extends unknown[] = []> = Arr extends [infer Item, ...infer RestArr]? [...Result, Camcelcase<Item & string>, ...CamelcaseArr<RestArr>]: Result;// CamelcaseUniontype CamelcaseUnion<Item extends string> =Item extends `${infer Left}_${infer Right}${infer Rest}`? `${Left}${Uppercase<Right>}$<CamelcaseUnion<Rest>`: Item;
联合类型的处理和对单个类型的处理没什么区别,TypeScript 会把每个单独的类型拆开传入。不需要像数组类型需要递归提取每一个元素处理
认知成本
// IsUnion 判断联合类型type IsUnion<A, B = A> =A extends A? [B] extends [A]? false: true: never;
这段逻辑就是分布式条件类型带来的认知成本
type TestUnion<A, B = A> = A extends A ? { a: A, b: B } : never;

A 和 B 都是同一个联合,但是值不一样。因为条件类型中如果左边的类型是联合类型,会把每个元素单独传入做计算,而右边不会。
所以 A 是 ‘a’ ,B 是 ‘a’ | ‘b’ | ‘c’,A 是 ‘b’,B 是 ‘a’ | ‘b’ | ‘c’ 等等。
所以 IsUnion 中,类型参数 A、B 是待判断的联合类型,B 默认值为 A,也就是同一个类型。
A extends A为了触发布分式条件类型,让 A 的每一个类型单独传入[B] extends [A]不直接写 B 就可以避免触发分布式条件类型,B 是整个联合类型
B 是联合类型整体,而 A 是单个类型,自然不成立,而其它类型没有这种特殊的处理, A 和 B 都是同一个,怎么判断都成立,利用这点判断出是否联合类型。
当 A 是联合类型时
- A extends A 这种写法是为了触发分布式条件类型,让每个类型单独传入处理,没别的意思
- A extends A 和 [A] extends [A] 是不同的处理,前者是单个类型和整个类型做判断,后者两边都是整个联合类型,因为只有 extends 左边直接是类型参数才会触发发布式条件类型。
数组转联合类型
通过索引访问来变为联合类型
type union = ['aaa', 'bbb'][number];// type union = "aaa" | "bbb";
// BEM CSS 命名规范,用 block__element--modifier 描述某个区块下面的某个元素的某个状态的样式// 如果传入是数组,要递归遍历取出每一个元素来和其它部分组合处理type BEM<Block extends string,Element extends string[],Modifier extends string[]> = `${Block}__${Element[number]}--${Modifier[number]}`;
全组合
// AllCombinations 全组合 'A'|'B' -> 'A'|'B'|'AB'|'BA'type Combination<A extends string, B extends string> =| A| B| `${A}${B}`| `${B}${A}`;// 任何两个类型的组合都有四种 A、B、AB、BAtype AllCombinations<A extends string, B extends string = A> = A extends A? Combination<A, AllCombinations<Exclude<B, A>>>: never;// 类型参数 A、B 是待组合两个联合类型,B 默认是 A 也就是同一个// A extends A 让联合类型每个类型单独传入做处理// A 的处理就是 A 和 B 中去掉 A 以后的所有类型组合,也就是 Combination<A,B 去掉 A 以后的所有组合>// B 去掉 A以后的所有组合就是 Combination<Exclude<B, A>>
