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

How to use variadic tuple types in 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))

这是一个带有函数的 playground,每个函数都有一个“测试”。他们使用的是非可变类型,这当然不太管用,而且我不知道如何编写它们才能使它们 起作用:
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() return 将 T 类型的值作为输入] 作为输出。

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

  • 编译器无法自行验证或理解 items.map(...) 会将 T 转换为 Wrapped<T>,反之亦然。请参阅 for details about why this is. Instead of trying to make the compiler do this, we simply assert map() 的 return 值是我们想要的类型(即 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