首页 > 解决方案 > 如何在 Typescript 中使用可变元组类型?

问题描述

我正在努力理解如何在 Typescript中使用可变元组类型。我试图通读文档,以及 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

标签: typescriptgenericsvariadic-functions

解决方案


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

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元组。因此wrap()wrapArray()取一个类型的值T作为输入,而unwrap()返回unwrapArray()一个类型的值T作为输出。

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

  • 编译器无法自行验证或理解items.map(...)将 aT变为 aWrapped<T>或反之亦然。有关原因的详细信息,请参阅此问题/答案。我们没有试图让编译器这样做,而是简单地断言的返回值map()是我们想要的类型(即,as Tas Wrapped<T>)。

  • wrap()任何unwrap()地方都没有使用可变参数元组类型。这种元组类型可以通过在元组中使用数组或元组类型的剩余参数来识别,例如[1, ...[2, 3, 4], 5]. wrapArray()andunwrapArray()函数使用可变元组类型作为输入(而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断言来允许编译器推断这items是一个正好包含两个元素的元组,第一个是 a {value: 4},第二个是 a {value: "foo"}。没有这个,编译器使用它的标准启发式假设一个数组是可变的并且它的长度和它的元素的身份可以改变......这意味着它items就像Array<{value: string} | {value: number}>并且没有希望将它解包成一个元组。

Playground 代码链接


推荐阅读