首页 > 解决方案 > 在 Typescript 中遍历可变参数元组

问题描述

编辑:此功能作为 TypeScript 4.1 的一部分提供,如 @jcalz 提到的。

我想做一个接受元组并遍历它的泛型类型。我的第一种方法是使用递归,但我得到了一个Type alias 'YourOperator' circularly references itself.. 这是我尝试过的最简单的例子

type VariadicAnd<T extends any[]> = T extends [infer Head, ...infer Tail] ? Head & VariadicAnd<Tail> : unknown

在我的具体情况下,我进一步想Head通过将它传递给另一个泛型类型来进行转换。例如:

type SimpleTransform<T> = { wrapped: T }
type VariadicAndWithTransform<T extends any[]> = T extends [infer Head, ...infer Tail]
  ? SimpleTransform<Head> & VariadicAndWithTransform<Tail>
  : unknown;

有趣的是,我的 IntelliSence 正在正确解析类型,但 typescript 编译器拒绝接受它。我想知道是否有另一种方法,或者是否有办法让我的递归工作。

标签: javascripttypescriptrecursiontuplestypescript-generics

解决方案


如上所述,在引入对递归条件类型的支持之后,您的版本在 TypeScript 4.1 及更高版本中按原样工作。

type VariadicAndWithTransform<T extends any[]> = T extends [infer F, ...infer R]
  ? SimpleTransform<F> & VariadicAndWithTransform<R>
  : unknown; // no error

type Works = VariadicAndWithTransform<[{ a: 1 }, { b: 2 }, { c: 3 }]>;
/* type Works = SimpleTransform<{
    a: 1;
}> & SimpleTransform<{
    b: 2;
}> & SimpleTransform<{
    c: 3;
}> */

有一些变通方法可以诱使编译器允许 4.1 之前的类型,但它们不受官方支持。如果你需要递归条件类型,你应该升级你的 TypeScript 版本。


但是对于此处所需的类型函数,您不需要递归类型。这实际上是一件好事,因为递归类型对编译器的负担更大,并且递归限制相当浅。如果你使用上面版本的VariadicAndWithTransform<T>whereT有几十个元素,你会看到错误,即使在 TS4.1+ 中:

type Three = [{}, {}, {}];
type Nine = [...Three, ...Three, ...Three];
type TwentySeven = [...Nine, ...Nine, ...Nine]
type UhOh = VariadicAndWithTransform<TwentySeven>; // error!
// -------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type instantiation is excessively deep and possibly infinite.

非递归版本对人类来说更难理解,但它使编译器更容易:

type VariadicAndWithTransform<T extends any[]> = {
  [K in keyof T]: (v: SimpleTransform<T[K]>) => void
}[number] extends ((v: infer I) => void) ? I : never

它的工作原理是从逆变位置(例如函数的参数)的联合中推断出条件类型,类似于此问题UnionToIntersection<T>的答案中的类型。

您可以验证它的行为是否与上面的示例相同:

type Works = VariadicAndWithTransform<[{ a: 1 }, { b: 2 }, { c: 3 }]>;
/* type Works = SimpleTransform<{
    a: 1;
}> & SimpleTransform<{
    b: 2;
}> & SimpleTransform<{
    c: 3;
}> */

而且因为它不使用递归,所以它在处理更长的元组时没有问题:

type StillWorks = VariadicAndWithTransform<TwentySeven>;
/* type StillWorks = { wrapped: {}; } */    

Playground 代码链接


推荐阅读