应用函数名称和参数的函数类型

Types for function that applys name of function and arguments

我正在尝试以正确的方式键入函数,该函数应用函数名称和该函数的参数。之后应用它并 return 结果。这里的代码:

const sum = (a: number, b: number) => a + b
const concat = (a: string, b: string, c: string) => a + b + c

const funs = {
    sum,
    concat
}

type Keys = 'sum' | 'concat'

type Args<T> = T extends (...args: infer R) => any ? R : never

type Sum = Args<typeof sum>
type Concat = Args<typeof concat>

function apply<K extends Keys>(funKey: K, ...args: Args<typeof funs[K]>) {
    // here I get the error 'An argument for 'a' was not provided.'
    return funs[funKey](...args)
}

const test1 = apply('sum', 1, 2)
const test2 = apply('concat', 'str1', 'str2', 'str3' )

内部函数 apply 我收到错误消息“未提供 'a' 的参数。”。 我怎样才能摆脱这个错误?

Link to playgound

编译器将无法理解这是类型安全的,因为它通常不能很好地推理依赖于尚未指定的泛型类型参数的类型的可分配性。现有 GitHub 问题 microsoft/TypeScript#24085 描述了这种情况。

事实上,有可能(但不太可能)在您的函数中,K 可能被推断为 Keys 本身,而不是 "sum""concat".如果你这样做:

const oops = apply(Math.random() < 0.5 ? "sum" : "concat", "a", "b", "c"); // oopsie
console.log(oops); // 50% chance of "abc", 50% chance of "ab"

然后您会看到编译器 技术上 更正了您正在做的事情类型不安全。你想告诉编译器 K 恰好是 Keys 的成员之一,但你不能。请参阅 microsoft/TypeScript#27808 以获取允许这样做的功能建议。

无论如何,编译器无法将 funKey 参数和 args 剩余参数视为具有 相关 类型。即使可以,它也不能很好地保持相关性,请参阅 microsoft/TypeScript#30581 了解更多信息。

它也无法理解计算 return 类型,因此您必须对其进行注释。您可以使用 ReturnType<F> utility type for this. Note that there's also a Parameters<F> utility type 代替您自己编写 Args<F>


因此,归根结底,您只需告诉编译器您正在做的事情是类型安全的(您不会在某些联合类型 funKey 上调用 apply() ,对吧?),因为它无法验证它。为此,您需要 type assertion. The easiest one to use here is good old any:

type Funs = typeof funs;

function apply<K extends Keys>(funKey: K, ...args: Parameters<Funs[K]>): ReturnType<Funs[K]> {
    return (funs[funKey] as any)(...args);
}

这会让你做出像return (funs[funKey] as any)(true)这样疯狂的事情,所以你要小心。稍微更安全但更复杂的是将 funs[funKey] 表示为一个函数,它以某种方式接受每个函数期望的参数,并且 returns both return 类型。像这样:

type WidenFunc<T> = ((x: T) => void) extends ((x: (...args: infer A) => infer R) => any) ?
    (...args: A) => R : never;

function apply<K extends Keys>(funKey: K, ...args: Parameters<Funs[K]>): ReturnType<Funs[K]> {
    return (funs[funKey] as WidenFunc<Funs[Keys]>)(...args);
}

这里WidenFunc<Funs[Keys]>(...args: [number, number] | [string, string, string]) => number & string。这是一种无意义的函数类型,但至少如果你向它传递一个像 (true) 而不是 (...args).

这样的参数它会抱怨

无论如何,其中任何一个都应该有效:

const test1 = apply('sum', 1, 2) // number
const test2 = apply('concat', 'str1', 'str2', 'str3') // string

好的,希望对您有所帮助;祝你好运!

Playground link to code