如何将联合类型转换为元组类型

时间:2019-03-12 17:02:50

标签: typescript

例如我输入了

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

在编译时如何使元组类型包含联合的所有元素?

type t = ['a','b', 'c'];

1 个答案:

答案 0 :(得分:5)

这是您不应该尝试做的真正错误的想法之一。让我们先做,然后骂自己:

// 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

type PushTuple = [[0], [0, 0], [0, 0, 0],
  [0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
];
type Push<
  T extends any[],
  V,
  L = PushTuple[T['length']],
  P = { [K in keyof L]: K extends keyof T ? T[K] : V }
  > = P extends any[] ? P : never;

type TuplifyUnion<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion1<Exclude<T, L>>, L>
type TuplifyUnion1<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion2<Exclude<T, L>>, L>
type TuplifyUnion2<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion3<Exclude<T, L>>, L>
type TuplifyUnion3<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion4<Exclude<T, L>>, L>
type TuplifyUnion4<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion5<Exclude<T, L>>, L>
type TuplifyUnion5<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion6<Exclude<T, L>>, L>
type TuplifyUnion6<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion7<Exclude<T, L>>, L>
type TuplifyUnion7<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion8<Exclude<T, L>>, L>
type TuplifyUnion8<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnion9<Exclude<T, L>>, L>
type TuplifyUnion9<T, L=LastOf<T>, N=[T] extends [never] ? true : false> = true extends N ? [] : Push<TuplifyUnionX<Exclude<T, L>>, L>
type TuplifyUnionX<T> = never

type abc = 'a' | 'b' | 'c';
type t = TuplifyUnion<abc>; // ["a", "b", "c"] 

Playground link

这种作品,但我真的真的建议不要将其用于任何官方目的或任何生产代码中。原因如下:

  • 您不能依赖联合类型的顺序。这是编译器的实现细节;由于X | Y等效于Y | X,因此编译器可以随意将一个更改为另一个。有时确实如此:

    type TypeTrue1A = TuplifyUnion<true | 1 | "a">; // [true, 1, "a"] 
    type Type1ATrue = TuplifyUnion<1 | "a" | true>; // [true, 1, "a"]!! 
    

    因此,实际上没有办法保留订单。

  • 您不能依赖于编译器认为的联合以及折叠或扩展的时间。 "a" | string只会折叠到string,而boolean实际上是扩展false | true

    type TypeAString = TuplifyUnion<"a" | string>; // [string]
    type TypeBoolean = TuplifyUnion<boolean>; // [false, true]
    

    因此,如果您打算保留一些现有元素,则应停止进行规划。没有一种通用的方法可以让元组进入工会并返回而又不会丢失此信息。

  • 没有通过通用联合进行迭代的受支持方法。我正在滥用所有conditional types的技巧。首先,我将联合A | B | C转换为函数()=>A | ()=>B | ()=>C的联合,然后使用intersection inference trick将函数联合转换为函数的交集()=>A & ()=>B & ()=>C,例如被解释为单个overloaded函数类型,并且使用条件类型提取返回值只能捕获 last 重载。所有这些疯狂最终导致A | B | C并只提取一种成分,可能是C。然后,您必须将其推入要构建的元组的末尾,然后...碰到了这一点:

  • 不支持通过对象的键集进行的一系列事情的类型迭代。显而易见的解决方案导致了循环类型别名forbidden。您可以使用恶作剧来欺骗编译器,使其不注意递归类型别名,但它是frowned upon。我已经尝试过了,但从来没有取得好成绩。因此,唯一不会使编译器爆炸的方法是选择一个将起作用的最大有限元组长度,例如10,然后将正常的递归定义展开到那么多几乎多余的行中。

所以你去了。您可以做到这一点,但不要这样做。 (如果您这样做这样做,如果发生爆炸,请不要怪我。)希望能有所帮助。祝你好运!