// oh boy don't do this
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type LastOf<T> =
UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never
// TS4.0+
type Push<T extends any[], V> = [...T, V];
// TS4.1+
type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> =
true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>
type abc = 'a' | 'b' | 'c';
type t = TuplifyUnion<abc>; // ["a", "b", "c"]
// Most of this is jcalz answer, up until the magic.
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
? I
: never;
type LastOf<T> =
UnionToIntersection<T extends any ? () => T : never> extends () => infer R
? R
: never;
type Push<T extends any[], V> = [...T, V];
type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> =
true extends N
? []
: Push<TuplifyUnion<Exclude<T, L>>, L>;
// The magic happens here!
export type Tuple<T, A extends T[] = []> =
TuplifyUnion<T>['length'] extends A['length']
? [...A]
: Tuple<T, [T, ...A]>;
5条答案
按热度按时间ppcbkaq51#
免责声明:不要这样做!!如果有人告诉你使用他们在这个答案中找到的代码做任何事情,除了证明为什么这是一个坏主意,走开!!
从元组类型转换为联合类型很容易;但是相反的,从一个联合体转换成一个元组是那些真正糟糕的想法之一,你不应该尝试去做。(参见microsoft/TypeScript#13298的讨论和规范答案)让我们先做这个,然后再自责:
Playground链接
这类的工作,但我真的真的真的不建议使用它为任何官方目的或在任何生产代码。
X | Y
等价于Y | X
,所以编译器可以随意地将两者进行转换,有时候它会这样做:所以真的没有办法保持顺序,而且请不要假设输出至少总是
[true, 1, "a"]
以上;这是无法保证的。这是一个实现细节,因此特定的输出可能会从一个版本的TypeScript到下一个版本,或者从一个代码编译到下一个版本。在某些情况下确实会发生这种情况:例如,编译器 * 缓存 * 联合;看起来不相关的代码可以影响并集的哪种排序被放入缓存,从而影响哪种排序出来。2排序不仅仅是不可靠。"a" | string
将折叠为string
,而boolean
实际上 * 展开 * 为false | true
:因此,如果你打算保留一些现有的元素数量,你应该停止这样的计划。没有一种通用的方法可以让一个元组往返于一个联合体而不丢失这些信息。
A | B | C
转换为一个函数的联合体,如()=>A | ()=>B | ()=>C
,然后使用交集推理技巧将该函数的联合体转换为一个函数的交集,如()=>A & ()=>B & ()=>C
,它被解释为一个单一的重载函数类型。而使用条件类型来提取返回值只会占用 * last * 重载,所有这些疯狂的操作最后都会占用A | B | C
并只提取一个组成部分,可能是C
,然后你必须将其推到正在构建的元组的末尾。So there you go. You can kind of do it, but don't do it. (And if you do do it, don't blame me if something explodes. 💣) Hope that helps. Good luck!
oyt4ldly2#
我有时候会遇到这样的情况:我想从类型A派生类型B,但发现要么TS不支持,要么进行转换会导致代码难以遵循。有时候,从A派生B的选择是任意的,我也可以从另一个方向派生。这里,如果您可以从元组开始,你可以很容易地派生出一个类型,它覆盖了元组接受的所有元素值:
如果你检查
AnyElementOfX
的展开,你会得到"a" | "b" | "c"
。hrysbysz3#
这种方法需要额外的工作,因为它是从实际的元组派生所需的元组类型的。
我同意。但是,它对相关用例仍然有用:* * 确保元组包含联合类型的所有元素**(例如,用于单元测试)。在这种情况下,您需要声明数组 * 无论如何 *,因为Typescript无法从类型生成运行时值。因此,"额外"工作变得没有意义。
注意:作为先决条件,我依赖于
TypesEqual
操作符as discussed here。为了完整起见,下面是我使用的版本,但还有其他选项:下面是实际的解决方案和使用示例:
一些警告:
rangeOf
不关心元组的排序,并且它允许重复条目(例如['b', 'a', 'c', 'a']
将满足rangeOf<Letters>()
)。对于单元测试,这些问题可能不值得关心。但是,如果你关心,你可以在返回values
之前对其进行排序和删除重复。这在初始化期间牺牲了少量的运行时性能,以获得"更干净"的表示。xdyibdwo4#
再进一步看jcalzanswer(同样,不要使用他的答案,他所有的警告都适用!),您实际上可以创建一些安全和可预测的东西。
这样做的原因是因为TypeScript中的
tuple
只是另一个Array
,我们可以比较数组的长度。现在,我们可以创建一个长度为联合成员数的元组,其中每个位置都是泛型类型
T
,而不是编译器行为导致的TuplifyUnion
的不可预测排序。不可否认,这仍然是不完美的,因为我们不能保证每个成员都是联合体的唯一成员,但是我们现在保证了联合体的所有成员的计数都将被结果元组覆盖,这是可预测的,也是安全的。
roqulrg35#
只是一种解决方案。这很简单,但是需要手工编写样板代码。
类型为
LazyStyleLoader
的任何对象必须包含ThemeName
联合的所有**成员作为属性。至少,您可以确定您的对象对于联合的每个成员总是具有一个属性。
然后你可以使用
Object.keys()
得到一个联合成员的元组: