联合类型在类型编程中比较特殊,TypeScript 对它做了专门的处理,写法上可以简化,但也增加一些认知成本。

分布式条件类型

当类型参数为联合类型,并且在条件类型左边直接引用该类型参数的时候,TypeScript 会把每一个元素单独传入来做类型运算,最后再合并成联合类型,这个种语法叫做分布式条伯类型。

  1. type Union = 'a' | 'b' | 'c';
  2. type str = `${Union}~~`;
  3. // type str = "a~~" | "b~~" | "c~~"

这样简化类型编程逻辑,不需要递归提取每个元素再处理。
联合类型的每个元素都是互不相关,不像数组、索引、字符串这些元素之间是有关系。设计成每一个独立处理,最后合并。

  1. // Camelcase
  2. type Camelcase<Str extends string> =
  3. Str extends `${infer Left}_${infer Right}${infer Rest}`
  4. ? `${Left}${Uppercase<Right>}${Camelcase<Rest>}`
  5. : Str;
  6. // CamelcaseArr
  7. type CamelcaseArr<
  8. Arr extends unknown[],
  9. Result extends unknown[] = []
  10. > = Arr extends [infer Item, ...infer RestArr]
  11. ? [...Result, Camcelcase<Item & string>, ...CamelcaseArr<RestArr>]
  12. : Result;
  13. // CamelcaseUnion
  14. type CamelcaseUnion<Item extends string> =
  15. Item extends `${infer Left}_${infer Right}${infer Rest}`
  16. ? `${Left}${Uppercase<Right>}$<CamelcaseUnion<Rest>`
  17. : Item;

联合类型的处理和对单个类型的处理没什么区别,TypeScript 会把每个单独的类型拆开传入。不需要像数组类型需要递归提取每一个元素处理

认知成本

  1. // IsUnion 判断联合类型
  2. type IsUnion<A, B = A> =
  3. A extends A
  4. ? [B] extends [A]
  5. ? false
  6. : true
  7. : never;

这段逻辑就是分布式条件类型带来的认知成本

  1. type TestUnion<A, B = A> = A extends A ? { a: A, b: B } : never;

image.png
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 左边直接是类型参数才会触发发布式条件类型。


数组转联合类型

通过索引访问来变为联合类型

  1. type union = ['aaa', 'bbb'][number];
  2. // type union = "aaa" | "bbb";
  1. // BEM CSS 命名规范,用 block__element--modifier 描述某个区块下面的某个元素的某个状态的样式
  2. // 如果传入是数组,要递归遍历取出每一个元素来和其它部分组合处理
  3. type BEM<
  4. Block extends string,
  5. Element extends string[],
  6. Modifier extends string[]
  7. > = `${Block}__${Element[number]}--${Modifier[number]}`;

全组合

  1. // AllCombinations 全组合 'A'|'B' -> 'A'|'B'|'AB'|'BA'
  2. type Combination<A extends string, B extends string> =
  3. | A
  4. | B
  5. | `${A}${B}`
  6. | `${B}${A}`;
  7. // 任何两个类型的组合都有四种 A、B、AB、BA
  8. type AllCombinations<A extends string, B extends string = A> = A extends A
  9. ? Combination<A, AllCombinations<Exclude<B, A>>>
  10. : never;
  11. // 类型参数 A、B 是待组合两个联合类型,B 默认是 A 也就是同一个
  12. // A extends A 让联合类型每个类型单独传入做处理
  13. // A 的处理就是 A 和 B 中去掉 A 以后的所有类型组合,也就是 Combination<A,B 去掉 A 以后的所有组合>
  14. // B 去掉 A以后的所有组合就是 Combination<Exclude<B, A>>