TS类型转换:union(联合)转tuple(元组)
需求
需求就是标题所示,需要有一个泛型类型能够把联合类型转化成一个相应顺序的元组类型:
1 | type SourceUnion = 'key1' | 'key2' | 'key3' |
代码
1 | /** |
思路
简单的来说就是使用递归算法,每次从联合类型(集合)中取出一个类型(元素),将该类型插入到已有的元组类型中,直至联合类型所有类型被取完;
难点
虽然思路说起来简单,但是由于TS
类型系统目前很多语法没有被提供,因此无法像手写JS
那样能够轻松遍历和递归之类的操作,因此会存在很多难点;
如何取出联合类型中的一个元素?
虽然联合类型从行为上来看可以视为集合,但是TS
本身并没有给联合类型提供类似从集合中取值的操作;
1 | type Union = 'a' | 'b' | 'c' |
所以要从联合类型中取出某个位置的元素,只能另辟蹊径;
-
重载的函数在使用
infer
进行推断时,重载的部分会取最后一个声明:1
2
3
4
5type FF = {
(): 'b';
(): 'a';
} // 一个重载的函数类型
type G = ReturnType<FF> // 'a'ReturnType
是内置的泛型类型,会返回函数的返回类型;1
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
-
将相同形状的函数类型进行交叉,等价于函数重载:
1
2
3
4
5
6type FF = {
(): 'b';
(): 'a';
}
type B = (() => 'b') & (() => 'a') // 函数的交叉
type G = FF extends B ? true : false // true
基于上述两个特性,就能够实现从联合类型中取出一个元素,只不过这个元素只能是最后一个:
-
将联合类型转为交叉函数类型(利用函数参数类型可以逆变的特性)
1
2
3
4
5
6
7/**
* 将联合类型转为对应的交叉函数类型
* @template U 联合类型
*/
type UnionToInterFunction<U> =
(U extends any ? (k: () => U) => void : never) extends
((k: infer I) => void) ? I : never -
获取交叉函数类型的返回类型
1
2
3
4
5/**
* 获取联合类型中的最后一个类型
* @template U 联合类型
*/
type GetUnionLast<U> = UnionToInterFunction<U> extends { (): infer A; } ? A : never
这样就可以做到获取联合类型中的最后一个元素了:
1 | type G = GetUnionLast<'a' | 'b'> // 'b' |
联合类型如何进行递归?
在TS类型系统中如果对(嵌套)对象类型进行递归,是可以直接调用自身的,如:
1 | type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> } |
如果泛型本身是联合类型,想要通过直接在类型中调用自身就会报错:
1 | // Type alias 'UnionToTuple' circularly references itself. |
上述递归方式TS
会报错,不过好在有一个迂回的解决办法:就是将三元运算结果包装成一个对象类型,然后使用索引访问结果:
1 | /** |