编译器将无法理解这是类型安全的,因为它通常不能很好地解释依赖于尚未指定的泛型类型参数的类型的可分配性。现有的 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 了解更多信息。
它也无法理解计算返回类型,因此您必须对其进行注释。您可以为此使用ReturnType<F> utility type。请注意,您还可以使用Parameters<F> utility type 而不是自己编写Args<F>。
因此,归根结底,您只需要告诉编译器您正在执行的操作是类型安全的(您不会在某些联合类型的 funKey 上调用 apply(),对吧?),因为它无法验证它。为此,您需要type assertion 之类的东西。这里最容易使用的是老旧的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] 表示为一个函数,该函数以某种方式接受每个函数预期的参数,并返回both的返回类型。像这样:
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