将类型数组转换为对象类型的联合

用户246185

假设我有这些类型:

type StepA = // ...
type StepB = // ...
type StepC = // ...

type Steps = [StepA, StepB, StepC]

我想要一个实用程序类型,SomeUtilityType<T>以便:

type StepsCombinations = SomeUtilityType<Steps>
// type StepsCombinations =
//   | { step: 0; value: StepA }
//   | { step: 1; value: StepB }
//   | { step: 2; value: StepC };

我应该如何定义SomeUtilityType<T>

编辑

如果Steps被定义为一个对象

type Steps = {0: StepA, 1: StepB, 2: StepC}

这个使用分布式条件类型的实用程序可以解决这个问题(参见playground):

type SomeUtilityType<
  V extends { [index: number]: unknown },
  T extends keyof V = keyof V
> = T extends unknown
  ? { step: T; value: V[T]; }
  : never;

但我更愿意避免使用多余的键定义对象0, 1 , 2...

我想我可以使用上面的类型定义,SomeUtilityType如果有某种方法可以获得数组中所有索引的联合,比如Indexes<Steps> // --> 0 | 1 | 2

游乐场链接

杰卡兹

您可以使用映射的元组类型来获得您想要的大部分内容。唯一棘手的部分是元组类型的已知键,无论出于何种原因,都是and这样的string 字面类型"0""123"而不是number0and这样的字面量123并且没有StrToNum<T>将前者转换为后者的实用程序类型。我之前评论过希望这些东西能够以编程方式处理元组。哦,好吧。

如果您可以使用字符串文字而不是数字文字,那么这很简单:

type SomeUtilityType<T extends readonly any[]> =
  { [K in keyof T]: { step: K, value: T[K] } }[number];

type StepsCombinations = SomeUtilityType<Steps>;
/* type StepsCombinations = {
    step: "0";
    value: StepA;
} | {
    step: "1";
    value: StepB;
} | {
    step: "2";
    value: StepC;
} */

另一方面,如果我们想要得到{step: 0, value: StepA}而不是{step: "0", value: StepA},我们需要自己进行转换。这是我可能会这样做的方式:

// only accepts steps up to 99, but you can extend if you want
type Nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
  12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
  23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 
  34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
  46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 
  58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
  69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 
  80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
  92, 93, 94, 95, 96, 97, 98, 99];

type SomeUtilityType<T extends readonly any[]> =
  { [K in keyof T]: { step: Nums[Extract<K, keyof Nums>], value: T[K] } }[number];

这使用硬编码的Nums元组将字符串转换为数字。它停在99,但您可以根据需要将其扩展到尽可能远的地方。您可以验证这StepsCombinations是您想要的输出:

type StepsCombinations = SomeUtilityType<Steps>
/* type StepsCombinations = {
    step: 0;
    value: StepA;
} | {
    step: 1;
    value: StepB;
} | {
    step: 2;
    value: StepC;
} */

您可能对硬编码列表不满意。好吧,您可以使用递归实用程序类型编写一个不同的版本来做一些概念上适用于任意长元组的事情

type SomeOtherUtilityType<T extends readonly any[], U = never> =
  T extends readonly [...infer I, infer L] ?
  SomeOtherUtilityType<I, { step: I['length'], value: L } | U> : U;

并验证它是否适用于您的示例:

type StepsCombinations2 = SomeOtherUtilityType<Steps>
/* type StepsCombinations2 = {
    step: 2;
    value: StepC;
} | {
    step: 1;
    value: StepB;
} | {
    step: 0;
    value: StepA;
} */

(是的,即使编译器以不同的顺序编写联合,它们也是相同的类型)。

不幸的是,硬编码列表版本表现更好。TypeScript 具有相当浅的递归限制,因此如果您传入长度超过 50 的元组,递归版本将中断:

type Okay = SomeUtilityType<Nums> // fine
type Oops = SomeOtherUtilityType<Nums> // error!
// Type instantiation is excessively deep and possibly infinite.

所以Nums如果你需要数字文字,我仍然会推荐这个版本

Playground 链接到代码

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章