如何在 Typescript 中使用可变元组类型?

问题描述

我正在努力理解如何在 Typescript 中使用 variadic tuple types。我试图通读文档和 GitHub 上的一些问题,但示例总是比“超级基本”更奇怪,所以我想知道是否有人可以帮我输入几个“超级基本”的,这样我就可以了也许用这些来“得到它”。

假设我们有这些功能

function wrap(...items) {
  return items.map(item => ({ value: item }))
}

function wrapArray(items) {
  return items.map(item => ({ value: item }))
}

function unwrap(...items) {
  return items.map(item => item.value)
}

function unwrapArray(items) {
  return items.map(item => item.value)
}

我该如何输入这些内容,以便例如以下内容可以按类型进行工作?

const items = [{ value: 4 },{ value: 'foo' }]

const [num,str] = unwrap(...items)
console.log(num.toFixed(2))
console.log(str.charat(0))

这是一个带有函数的游乐场,以及每个函数的“测试”。他们使用的是非可变类型,这当然不是很有效,而且我不明白如何编写它们以便它们工作:
TypeScript Playground

解决方法

以下是我倾向于输入它们的方式:

type Wrapped<T extends any[]> = { [I in keyof T]: { value: T[I] } };

function wrap<T extends any[]>(...items: T) {
    return items.map(item => ({ value: item })) as Wrapped<T>;
}

function unwrap<T extends any[]>(...items: Wrapped<T>) {
    return items.map(item => item.value) as T;
}

function wrapArray<T extends any[]>(items: readonly [...T]) {
    return items.map(item => ({ value: item })) as Wrapped<T>
}

function unwrapArray<T extends any[]>(items: readonly [...Wrapped<T>]) {
    return items.map(item => item.value) as T;
}

备注:

  • 这些函数在 T 中都是通用的,即非包装元素的 tuple。所以 wrap()wrapArray()T 类型的值作为输入,而 unwrap()unwrapArray() 返回 T 类型的值作为输出。

  • Wrapped<T> 是一个 mapped tuple,它包装了 T 的每个元素。 wrap()wrapArray() 返回 Wrapped<T> 类型的值作为输出,而 unwrap()unwrapArray()Wrapped<T> 类型的值作为输入。

  • 编译器本身无法验证或理解 items.map(...) 会将 T 变成 Wrapped<T>,反之亦然。有关原因的详细信息,请参阅 this question/answer。我们没有试图让编译器这样做,而是简单地 assert map() 的返回值是我们想要的类型(即,as Tas Wrapped<T> ).

  • wrap()unwrap() 中没有任何地方使用 variadic tuple types。这种元组类型可以通过使用元组内的数组或元组类型的其余参数来识别,例如 [1,...[2,3,4],5]wrapArray()unwrapArray() 函数使用可变元组类型作为输入(readonly [...T] 而不仅仅是 T),但这只是为了在使用它们时帮助推断元组,而不是必要的。因此,当您询问可变参数元组类型时,该示例不需要使用它们。


让我们试一试:

const items = [{ value: 4 },{ value: 'foo' }] as const; // <-- note well
const [num,str] = unwrap(...items);
// or const [num,str] = unwrapArray(items);
console.log(num.toLocaleString());
console.log(str.charAt(0));

这现在可以工作了,但请注意,我必须使用 const assertion 来让编译器推断 items 是正好有两个元素的元组,第一个是 {value: 4}第二个是 {value: "foo"}。没有它,编译器使用它的标准启发式假设一个数组是可变的,它的长度和它的元素的身份可以改变......这意味着 items 类似于 Array<{value: string} | {value: number}> 并且没有希望将其解包成一个元组。

Playground link to code