联合类型的分布式

222 阅读2分钟

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

TypeScript 对联合类型在条件类型中使用时的特殊处理:会把联合类型的每一个元素单独传入做类型计算,最后合并。

1、分布式条件类型例子

1、在条件类型左边直接引用联合类型

type Union36 = 'a' | 'b' | 'c';

//想把其中的 a 大写
type UppercaseA36<Item extends string> =
  Item extends 'a' ? Uppercase<Item> : Item;

type testUnion36 = UppercaseA36<Union36>;
//type testUnion36 = "b" | "c" | "A"

2、联合类型遇到字符串时

//联合类型遇到字符串时
type Union36 = 'a' | 'b' | 'c';
type str36 = `${Union36}**`
//type str36 = "a**" | "b**" | "c**"

TypeScript 之所以这样处理联合类型也很容易理解,因为联合类型的每个元素都是互不相关的,不像数组、索引、字符串那样元素之间是有关系的。所以设计成了每一个单独处理,最后合并。

2、把联合类型改成驼峰

//把str改为驼峰
type Camelcase36<Str extends string> =
  Str extends `${infer Left}_${infer Right}${infer Rest}`
  ? `${Left}${Uppercase<Right>}${Camelcase36<Rest>}`
  : Str;

//把字符串数组改成驼峰
type CamelcaseArr36<
  Arr extends unknown[]
> = Arr extends [infer Item, ...infer RestArr]
  ? [Camelcase36<Item & string>, ...CamelcaseArr36<RestArr>]
  : [];
type testCamelcaseArr36 = CamelcaseArr36<['hello_my_name_is_Lily', 'thank_you_very_much', 'you_are_welcome']>;
//type testCamelcaseArr36 = ["helloMyNameIsLily", "thankYouVeryMuch", "youAreWelcome"]

//联合类型不需要递归提取每个元素
type CamelcaseUnion36<Item extends string> =
  Item extends `${infer Left}_${infer Right}${infer Rest}`
  ? `${Left}${Uppercase<Right>}${CamelcaseUnion36<Rest>}`
  : Item;
type testCamelcaseUnion36 = CamelcaseUnion36<'hello_my_name_is_Lily' | 'thank_you_very_much' | 'you_are_welcome'>;
//type testCamelcaseUnion36 = "helloMyNameIsLily" | "thankYouVeryMuch" | "youAreWelcome"

3、如何判断联合类型

A extends A 这段看似没啥意义,主要是为了触发分布式条件类型,让 A 的每个类型单独传入。

[B] extends [A] 这样不直接写 B 就可以避免触发分布式条件类型,那么 B 就是整个联合类型。

//A extends A 这段看似没啥意义,主要是为了触发分布式条件类型,让 A 的每个类型单独传入。
//[B] extends [A] 这样不直接写 B 就可以避免触发分布式条件类型,那么 B 就是整个联合类型。
//比如下面:
type TestUnion36<A, B = A> = A extends A ? { a: A, b: B } : never;
type TestUnionResult36 = TestUnion36<'a' | 'b' | 'c'>;
/*
type TestUnionResult36 = {
    a: "a";
    b: "a" | "b" | "c";
} | {
    a: "b";
    b: "a" | "b" | "c";
} | {
    a: "c";
    b: "a" | "b" | "c";
}
A 和 B 都是同一个联合类型,为啥值还不一样呢?
因为条件类型中如果左边的类型是联合类型,会把每个元素单独传入做计算,而右边不会。
*/

//那么利用这个特点就可以实现 Union 类型的判断:
type IsUnion36<A, B = A> =
  A extends A
  ? [B] extends [A]
  ? false
  : true
  : never
//B 是联合类型整体,而 A 是单个类型,自然不成立
//而其它类型没有这种特殊处理,A 和 B 都是同一个,怎么判断都成立。
//利用这个特点就可以判断出是否是联合类型。

4、数组转联合类型

数组后面加个[number]

//数组转联合类型
type arrTransferUnion = ['aaa', 'bbb', 'ccc'][number]
//type arrTransferUnion = "aaa" | "bbb" | "ccc"

5、联合类型实现BEM

em 是 css 命名规范,用 block__element--modifier 的形式来描述某个区块下面的某个元素的某个状态的样式。

那么我们可以写这样一个高级类型,传入 block、element、modifier,返回构造出的 class 名:

这样使用:

type bemResult = BEM<'guang', ['aaa', 'bbb'], ['warning', 'success']>;

它的实现就是三部分的合并,但传入的是数组,要递归遍历取出每一个元素来和其他部分组合,这样太麻烦了。如果是联合类型就不用递归遍历了,因为联合类型遇到字符串也是会单独每个元素单独传入做处理。

//联合类型实现BEM
type BEM36<
  Block extends string,
  Element extends string[],
  Modifiers extends string[]
> = `${Block}__${Element[number]}--${Modifiers[number]}`;
//类型参数 Block、Element、Modifiers 分别是 bem 规范的三部分
//其中 Element 和 Modifiers 都可能多个,约束为 string[]。
//字符串类型中遇到联合类型的时候,会每个元素单独传入计算

type bemResult=BEM36<'divContent',['li','span'],['warning','success']>
//type bemResult = "divContent__li--warning" | "divContent__li--success" | "divContent__span--warning" | "divContent__span--success"

6、实现全组合

希望传入 'A' | 'B' 的时候,能够返回所有的组合: 'A' | 'B' | 'BA' | 'AB'。

这种全组合问题的实现思路就是两两组合,组合出的字符串再和其他字符串两两组和:

比如 'A' | 'B' | 'c',就是 A 和 B、C 组合,B 和 A、C 组合,C 和 A、B 组合。然后组合出来的字符串再和其他字符串组合。

任何两个类型的组合有四种:A、B、AB、BA

type Combination36<A extends string, B extends string> =
  | A
  | B
  | `${A}${B}`
  | `${B}${A}`;
type testCombination36 = Combination36<'A', 'BC'>;
//type testCombination36 = "A" | "BC" | "ABC" | "BCA"

type AllCombinations36<A extends string, B extends string = A> =
  A extends A
  ? Combination36<A, AllCombinations36<Exclude<B, A>>>
  : never;
//A extends A 的意义就是让联合类型每个类型单独传入做处理,上面我们刚学会。
//A 的处理就是 A 和 B 中去掉 A 以后的所有类型组合
//也就是 Combination<A, B 去掉 A 以后的所有组合>。
//而 B 去掉 A 以后的所有组合就是 AllCombinations<Exclude<B, A>>
//所以全组合就是 Combination<A, AllCombinations<Exclude<B, A>>>

type testAllCombinations36 = AllCombinations36<'A' | 'B' | 'C'>
//type testAllCombinations36 = "A" | "B" | "C" | "BC" | "CB" | "AB" | "AC" | "ABC" | "ACB" | "BA" | "CA" | "BCA" | "CBA" | "BAC" | "CAB"

有两点特别要注意:

  • A extends A 不是没意义,意义是取出联合类型中的单个类型放入 A
  • A extends A 才是分布式条件类型, [A] extends [A] 就不是了,只有左边是单独的类型参数才可以。