type Union = 'a' | 'b' | 'c';
// 转
type Tuple = ['a', 'b', 'c'];
如果只是不想重复声明可以尝试转变下思路,可以使用数值来获取两种类型:
export const tuple = <T extends string[]>(...args: T) => args;
const arr = tuple('a', 'b', 'c');
type Tuple = typeof arr;
// type Tuple = ["a", "b", "c"]
type Union = typeof arr[number]
// type Union = "a" | "b" | "c"
答案
传送门:Code in ts playground
为了解决这个题,去查了不少资料。一直想尝试用一种新的看似更简单的方法来解决这个问题,最后发现不太可能,所以还是这个网上能找到的答案,顶多只是写法略有不同。
本来想利用
U extends any
会遍历 U 中的原始类型的特点(TypeScript: Distributive conditional types,通过递归组合成[...]
,结果失败。它最后只能得出联合,不能单个遍历。仔细想一下,在 TypeScript 能遍历的好像只有
{ ... }
的属性,但这个问题似乎又没办法封装成对象接口类型来处理。想来想去,好像只能通过上面UnionLast
所使用的方法,使用对函数参数的逆变推导来得到联合中的单一类型。OK 这里又涉及到了协变和逆变的知识点,而要搞明白协变和协变,还得清楚类型的父/子级关系,也就是 extends 关系。具体的可以搜,这里总结两点:在这个基础之上,可以理解
A
是A | B | C
的子类型。才能够理解UnionLast
可以通过函数参数的逆变,从联合类型中推断出最后一个符合的类型A
,相当于从一个数组中找到第一个符合条件的元素,而条件是“非 never”,也就是找第 1 个元素。然后 UnionToTuple 干的就是不断找第一个元素,推入 Tuple,再把这个元素从原类型集合中去掉,递归行进……接下来就需要理解 UnionToIntersection 的作用了,看这篇:Ts高手篇:22个示例深入讲解Ts最晦涩难懂的高级类型工具 - 掘金 (juejin.cn)。这里是转相交类型的方法,加上前面提到的需要通过逆变来推导类型,而逆变又只能通过函数参数来实现,所以答案中算的是若干函数的相交类型。
最后还是有一点遗憾 —— 不能掌握结果 Tuple 的顺序,而且结果 Tuple 的顺序似乎并不稳定。注意下面动画中的 Tuple3,一开始是
["a", "b", undefined]
,改了 Union 之后它变成了[undefiend, "a", "b"]
(注意:Tuple3 跟 Union 本身一点关系都没有)参考