当在条件类型的左边直接引用联合类型的时候,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] 就不是了,只有左边是单独的类型参数才可以。