TypeScript 将命名元组转换为对象字面量类型,

fcg9iug3  于 6个月前  发布在  TypeScript
关注(0)|答案(9)|浏览(60)

搜索词

重构

建议

提供一个将命名元组转换为对象字面量类型的重构。

使用场景

现在我们有命名元组。
编写元组以传递多个值是很常见的。
随着时间的推移,您可能会向元组中添加许多其他字段。
您不能在元组的中间或末尾取子序列。
如果我们能将其转换为对象,那将会很有用。

示例

type Tuple = [number, number]

function aggregate(items: number[]): Tuple {
  // ...
  return [min, max]
}

const [ min, max ] = aggregate(items)

增长到

type Tuple = [number, number, number, number, number, number]

function aggregate(items: number[]): Tuple {
  // ...
  return [min, max, avg, mid, first, last]
}

const [ min, max, avg, mid, first, last ] = aggregate(items)

如果我们只想获取第一个和最后一个

const [ , , , , first, last ] = aggregate(items) // too many '.'

const result = aggregate(items);
const first = result[4] // magic number
const last = result[5]  // magic number

如果我们有这个重构

// add names into tuple
type Tuple = [min: number, max: number, avg: number, mid: number, first: number, last: number]

// apply refactor
type Tuple = {min: number, max: number, avg: number, mid: number, first: number, last: number}

function aggregate(items: number[]): Tuple {
  // ...
  return {min, max, avg, mid, first, last}
}

const { first, last } = aggregate(items)

检查清单

我的建议满足以下准则:

  • 这不会对现有的TypeScript/JavaScript代码造成破坏性变化
  • 这不会改变现有JavaScript代码的运行时行为
  • 我们可以在不根据表达式的类型发出不同的JS的情况下实现这一点
  • 这不是一个运行时特性(例如库功能、带有JavaScript输出的非ECMAScript语法等)
  • 这个特性将与 TypeScript's Design Goals 的其他部分保持一致。
uwopmtnx

uwopmtnx1#

此外,数组解构在底层通过调用 [Symbol.iterator]() 使用 for‑of ,而对象解构则使用简单的 [[Get]] 操作。

z18hc3ub

z18hc3ub2#

一个好的解决方案可能取决于元组标签访问的内在效用类型。

type X = [first: string, second: number];
type Label0 = Label<X, 0>; // "first"
type Label0 = Label<X, 1>; // "second"

可以像这样执行Map:

type XAsRec = {
  [I in Extract<keyof X, number> as Label<X, I>]: X[I];
} // {first: string; second: number}
yx2lnoni

yx2lnoni3#

看起来这和type-level documentation问题非常相似,因为我们可能想要解包、附加和替换标签,即使它们没有相应的JS值(就像文档一样)。

type WithoutLabels = ["a", "b", "c"];

type WithLabels = [
  Labeled<WithoutLabels[0], "first">,
  Labeled<WithoutLabels[1], "second">,
  Labeled<WithoutLabels[2], "third">
];

type FirstLabel = WithLabels[0] extends Labeled<any, infer Label> ? Label : never; // "first"

一个内在的Labeled类型,可以同时附加和解包标签,在我看来最有意义。@tjjfvi,对此有什么想法吗?

1mrurvl1

1mrurvl14#

内在的 Labeled 类型在我看来似乎有些奇怪,因为标签是元组的一个方面,而不是元素。或许更合理的做法是使用实用类型 LabeledTuple<[A, B, C], ["a", "b", "c"]> = [a: A, b: B, c: C],可以像这样使用:

type LabelsOfTuple<T extends any[]> = T extends LabeledTuple<T, infer L> ? L : never
type TupleToObject<T extends any[]> = {
  [K in keyof T & number as LabelsOfTuple<T>[K]]: T[K]
}

话虽如此,虽然 LabeledTuple 实用类型很好,但我认为 TupleToObject 类型并没有完全解决这个问题的使用案例,因为它不会改变数组解构为对象解构。
关于原始问题,一个考虑因素是 [a: 1, a: 1] 是有效的,所以那些元组必须被排除或者特殊处理。

dohp0rv5

dohp0rv55#

关于 LabeledTuple 的讨论:另请参阅 #44939

gj3fmq9x

gj3fmq9x6#

另一个用例是 Parameters<fn> 实用程序调用 - 它返回一个命名元组,但偶尔我想将一长串参数(对于库来说超出了我的控制范围)转换为 Package 器对象 - 如果我能为 TupleToObject<Parameters<fn>> 做额外的一个 Package 器,那么这将稍微简化类型,并确保如果参数发生变化时它们始终正确(尽管目前它们可能会发生变化,但类型可能没有变化到足以触发编译器警告的程度)。

vc9ivgsu

vc9ivgsu7#

@Rycochet说了什么?👍

9njqaruj

9njqaruj8#

+1 这仍然非常有帮助-我有一个案例,我想从Parameters<>中获取一个对象类型。
对此有任何进展吗?

8cdiaqws

8cdiaqws9#

请确认,目前没有解决这个问题的临时方法吗?这使得 Parameter<T> 的使用变得有些困难。
类似于 { [K in Exclude<keyof P, keyof any[]>]: P[K] } 的功能只是给出索引。
即使有类似 labelof 的功能或者能提供很大帮助的东西。

相关问题