【问题标题】:How to use variadic tuple types in Typescript?如何在 Typescript 中使用可变元组类型?
【发布时间】:2021-06-29 22:47:25
【问题描述】:

我很难理解如何在 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

【问题讨论】:

    标签: typescript generics variadic-functions


    【解决方案1】:

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

    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&lt;T&gt; 是一个mapped tuple,它包装了T 的每个元素。 wrap()wrapArray() 返回 Wrapped&lt;T&gt; 类型的值作为输出,而 unwrap()unwrapArray()Wrapped&lt;T&gt; 类型的值作为输入。

    • 编译器无法自行验证或理解items.map(...) 会将T 转换为Wrapped&lt;T&gt;,反之亦然。有关原因的详细信息,请参阅this question/answer。我们没有试图让编译器这样做,而是简单地assert 表明map() 的返回值是我们想要的类型(即as Tas Wrapped&lt;T&gt;)。

    • 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},第二个是 @ 987654361@。没有这个,编译器使用它的标准启发式假设一个数组是可变的,并且它的长度和它的元素的身份可以改变......这意味着items类似于Array&lt;{value: string} | {value: number}&gt;并且没有希望将它展开成一个元组。

    Playground link to code

    【讨论】:

    • 看到它起作用了,我认为有两件事仍然让我很失望......第一个是映射元组Wrapped&lt;T&gt;。它有什么作用,在这种情况下,为什么必须使用它而不是简单的(非工作)type Wrapped&lt;T&gt; = { value: T }?第二个是,为什么你在wrapArray 中使用[...T],而不是在wrap 中? T[...T] 这里有什么区别?
    • Wrapped&lt;[string, number]&gt; 变为 [{value: string}, {value: number}] 而您的版本将变为 {value: [string, number]} 这不是您想要的。您想跨元组 T 的元素映射您的版本;因此映射的元组。
    • 如果我调用wrapArray([1, 2, 3]),输入args: T 最终会使编译器推断T 就像number[],因为默认情况下编译器不倾向于推断数组的元组。键入args: [...T]args: readonly [...T] 是对编译器的提示,您希望T 改为[number, number, number]。我不使用wrap()unwrap() 这样做,因为像...args: T 这样的rest 参数已经被推断为一个元组(因为默认情况下您通常关心函数的参数顺序)。
    • 啊啊啊。太棒了,谢谢你!想想现在开始变得更清晰了! ?
    猜你喜欢
    • 1970-01-01
    • 2021-04-15
    • 2021-03-09
    • 1970-01-01
    • 2017-05-15
    • 2022-01-05
    • 1970-01-01
    • 2019-07-09
    • 2022-12-18
    相关资源
    最近更新 更多