TypeScript:是否可以为每个数组元素单独推断一个类型参数?

mi7gmzs6  于 2023-04-13  发布在  TypeScript
关注(0)|答案(2)|浏览(86)

假设我们有以下代码:

// a value, and a function that will be called with this value
type ValueAndHandler<T> = [value: T, handler: (value: T) => void]

type Params = {
    // not sure how to type it
    valuesAndHandlers: ValueAndHandler<???>[]
    // it's not the only field in the structure
    // so I cannot just pass array as variadic arguments
    otherStuff: number
}

function doSomethingWithParams(params: Params){/* something, not important*/}

doSomethingWithParams({
    // array of some pairs of handlers and values
    // each value is only related to its handler, and not to other handlers/values
    valuesAndHandlers: [
        [5, value => console.log(value.toFixed(3))],
        ["example", value => console.log(value.length)]
    ],
    otherStuff: 42
})

在这段代码中,我希望第一个处理程序的参数类型被推断为number,第二个处理程序的参数类型被推断为string;但这并没有发生(因为我在变量定义中将它们输入为unknown)。
如何键入定义以便分别推断每个处理程序的正确类型?
我尝试为泛型参数添加一个默认类型,但没有帮助,因为它对所有对都是一样的。我还尝试使用satisfies,而不显式输入变量,但效果一样。我也见过this question,但无法将该技术应用于我的情况。

wz3gfoph

wz3gfoph1#

如果你想要像这样的推断,你没有太多的选择,除了旋转一个 Package 器fn:

function valueAndHandlers<T extends any[]>(
  ...arg:{ [I in keyof T]: [T[I], (arg: T[I]) => void] }
) {
  return [arg]
}

// This will return them wrapped in an outer array. Need to use varargs to get inference I think.

valueAndHandlers(
  [5, value => console.log(value.toFixed(3))],
  ["example", value => console.log(value.length)]
) 

// As used in your edit

doSomethingWithParams({
    // array of some pairs of handlers and values
    // each value is only related to its handler, and not to other handlers/values
    valuesAndHandlers: valueAndHandlers(
        [5, value => console.log(value.toFixed(3))],
        ["example", value => console.log(value.length)]
    )
})
sauutmhj

sauutmhj2#

假设你真的想跟踪数组中每个元素的generic类型参数的元组,那么你必须在该元组类型中使Params成为泛型:

type Params<T extends any[]> = {
  valuesAndHandlers: [...{ [I in keyof T]: ValueAndHandler<T[I]> }]
}

这里的valuesAndHandlers属性是一个Map的元组类型,其中输入类型T的每个元素T[I](在索引I处)都被ValueAndHandler Package 成ValueAndHandler<T[I]>。这并没有真正改变类型,但它确实给予了编译器一个提示,即当它推断类型时,它应该尝试推断元组而不是无序的任意长度数组类型(这在microsoft/TypeScript#39094:“[...T]类型,其中T是一个类似数组的类型参数,可以方便地用于指示元组类型推断的首选项”)。
为了减少需要编写的类型注解的数量,我们可以创建一个helper函数来推断特定的T参数,给定一些Params<T>

const asParams = <T extends any[]>(p: Params<T>) => p;

我们可以这样使用它:

let params = asParams({
  valuesAndHandlers: [
    [5, (value: number) => console.log(value.toFixed(3))],
    ["example", (value: string) => console.log(value.length)]
  ]
});
/* let params: Params<[number, string]> */

因此,params被推断为Params<[number, string]>
请注意,为了使它工作,我必须在回调中注解参数类型,这种从上下文推断value => console.log(value.length)value类型的上下文类型不适用于从属性内部Map元组的推断。根据您的关注程度,可以通过重构asParams()函数以接受valuesAndHandlers属性值作为单独的参数来减轻这种情况......或者实际上,任何数量的可能的重构来调整推断。
这里的主要概念点是Params需要是通用的,其余部分主要是尝试获得所需推断的技术。
Playground链接到代码

相关问题