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

Iva*_*van 4 typescript typescript-generics typescript-typings typescript-types

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

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' )
Run Code Online (Sandbox Code Playgroud)

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

游乐场链接

jca*_*alz 7

编译器将无法理解这是类型安全的,因为它通常不能很好地推理依赖于尚未指定的泛型类型参数的类型的可分配性。现有的 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"
Run Code Online (Sandbox Code Playgroud)

然后你会看到编译器在技术上是正确的,你所做的不是类型安全的。你想告诉编译器K成员只有一个 Keys,而你不能。有关允许这样做的功能建议,请参阅microsoft/TypeScript#27808

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

它也无法理解计算返回类型,因此您必须对其进行注释。您可以为此使用ReturnType<F>实用程序类型。请注意,还有一种Parameters<F>实用程序类型,您可以使用它来代替Args<F>自己编写。


因此,归根结底,您只需要告诉编译器您所做的是类型安全的(您不会调用apply()某些联合类型funKey,对吧?),因为它无法验证它。要做到这一点,您需要诸如类型断言之类的东西。在这里最容易使用的是 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);
}
Run Code Online (Sandbox Code Playgroud)

这会让你做一些疯狂的事情,比如return (funs[funKey] as any)(true),所以你应该小心。稍微更类型安全但更复杂的是表示funs[funKey]为一个函数,该函数以某种方式接受每个函数期望的参数,并返回两种返回类型。像这样:

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);
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

Playground 链接到代码