我正在构建一个组合函数,该函数产生输入列表的carried乘积.作为一个怪癖,我想支持枚举作为潜在列表.我可能会在稍后扩展它以支持实际的集合.

我有一个非常有效的代码:

type EnumOrArray = { [key: string]: any; } | any[];

export function cartesian(...inputs: EnumOrArray[]): any[][] {
  const itemGroups: any[][] = inputs.map(input => {
    if (Array.isArray(input)) {
      return input; // Directly return if input is an array
    } else {
      // Only work with the enum's keys (names), then convert them to their numeric values
      const originalKeys = Object.keys(input).filter(key => isNaN(Number(key)));
      return originalKeys.map(key => input[key]);
    }
  });

  // Recursive function to generate combinations (remains unchanged)
  function generateCartesianProduct(groups: any[][], prefix: any[] = []): any[][] {
    if (!groups.length) return [prefix];
    const firstGroup = groups[0];
    const restGroups = groups.slice(1);

    let result: any[][] = [];

    firstGroup.forEach(item => {
      result = result.concat(generateCartesianProduct(restGroups, [...prefix, item]));
    });

    return result;
  }

  return generateCartesianProduct(itemGroups);
}

...但这是一个类型的防火墙,只生成any [][]类型的输出,这并不令人满意.

例如,如果我使用这个:

  enum Color {
    Red = 'red',
    Green = 'green',
    Blue = 'blue'
  }
  enum Size {
    Small = 1,
    Medium = 10,
    Large = 100
  }
  const numbers = [1, 2];
  const combos = cartesian(Color, numbers, Size);

它产生了正确的价值:

[
  [ 'red', 1, 1 ],     [ 'red', 1, 10 ],
  [ 'red', 1, 100 ],   [ 'red', 2, 1 ],
  [ 'red', 2, 10 ],    [ 'red', 2, 100 ],
  [ 'green', 1, 1 ],   [ 'green', 1, 10 ],
  [ 'green', 1, 100 ], [ 'green', 2, 1 ],
  [ 'green', 2, 10 ],  [ 'green', 2, 100 ],
  [ 'blue', 1, 1 ],    [ 'blue', 1, 10 ],
  [ 'blue', 1, 100 ],  [ 'blue', 2, 1 ],
  [ 'blue', 2, 10 ],   [ 'blue', 2, 100 ]
]

但我希望它是[Color, number, Size][]型的,而不是any[][]型的.

我认为答案是用可变泛型类型做一些聪明的事情,但我想不出来.

推荐答案

在下面的内容中,我将假设cartesian的实现已经完成,并且您只是在寻找调用签名的类型.编译器将无法真正验证实现是否真的匹配调用签名,因此在函数中,您可能需要type assertionsthe any type或类似的值来避免编译器错误.


我在这里采取的方法是在inputs数组的tuple type T中设置函数generic,然后让函数返回一个mapped array type的数组,该数组将T的每个元素转换为相应的输出类型:

declare function cartesian<T extends EnumOrArray[]>(...inputs: T):
  { [I in keyof T]: ConvertEnumOrArrayToElement<T[I]> }[];

如果inputs是类型[A, B, C],那么返回类型将是[ConvertEnumOrArrayToElement<A>, ConvertEnumOrArrayToElement<B>, ConvertEnumOrArrayToElement<C>][]. 现在我们需要定义ConvertEnumOrArrayToElement.


ConvertEnumOrArrayToElement<T>的目标是T应该是EnumOrArray的某个子类型. 如果T是类型U[]的数组,那么我们需要U.T{[k: string]: any}类型.简单地说,你想要T[keyof T],所有属性值类型T中的union个.But这里的棘手之处在于,在代码中,你明显地 suppress 了任何数字键,以避免数字枚举的reverse mapping. 所以我们需要探测T中的数字键,并将其 suppress . 而不是T[keyof T],我们将有X[keyof T],其中X就像T,除了没有来自数字键的属性贡献:

type ConvertEnumOrArrayToElement<T extends EnumOrArray> =
  T extends (infer U)[] ? U :
  { [K in keyof T]: K extends number ? never : T[K] }[keyof T]

这里我们使用conditional type来区分T是数组和T是其他对象. 我使用conditional type inference with infer提取数组元素类型U(尽管T[number]也可以). 对于非数组,我们有另一个mapped type来判断数字键K并 suppress 它们的属性类型(通过将其映射到the never type,后者在联合中消失).


好吧,我们来测试一下:

enum Color {
  Red = 'red',
  Green = 'green',
  Blue = 'blue'
}

enum Size {
  Small = 1,
  Medium = 10,
  Large = 100
}
const numbers = [1, 2];
const combos = cartesian(Color, numbers, Size);
//    ^? const combos: [Color, number, Size][]

看起来不错这就是你想要的输出类型. 为了清楚起见,这里是你用ConvertEnumOrArrayToElementinputs的元素得到的结果:

type ColorOutput = ConvertEnumOrArrayToElement<typeof Color>
// type ColorOutput = Color
type NumbersOutput = ConvertEnumOrArrayToElement<typeof numbers>
// type NumbersOutput = number
type SizeOutput = ConvertEnumOrArrayToElement<typeof Size>
// type SizeOutput = Size

因此,它根据需要将数组转换为它们的元素类型,并将枚举转换为它们的枚举类型.

Playground link to code

Typescript相关问答推荐

用泛型类型覆盖父类函数导致类型y中的Property x不能赋给基类型Parent中的相同属性."'''''' "

如何在使用`Next—intl`和`next/link`时导航

在映射类型中用作索引时,TypeScrip泛型类型参数无法正常工作

如果字符串文字类型是泛型类型,则用反引号将该类型括起来会中断

通过按键数组拾取对象的关键点

使用Dockerfile运行Vite React应用程序时访问env变量

如何从MAT-FORM-FIELD-MAT-SELECT中移除焦点?

无法将从服务器接收的JSON对象转换为所需的类型,因此可以分析数据

如何编辑切片器以使用CRUD调用函数?

使用ngfor循环时,数据未绑定到表

抽象类对派生类强制不同的构造函数签名

如何将通用接口的键正确设置为接口的键

埃斯林特警告危险使用&as";

是否可以定义引用另一行中的类型参数的类型?

如何创建由一个帐户签名但使用另一个帐户支付的Hedera交易

typescript如何从映射类型推断

TS 排除带点符号的键

从 npm 切换到 pnpm 后出现Typescript 错误

在 TypeScript 中实现类型级别深度优先搜索

显式推断元组类型