这个问题有两个部分,首先是获取函数的公共签名权。我们希望函数接收some.Api 的键并返回与原始键相同类型的值。为此,我们需要一个扩展 keyof some.Api 的额外类型参数 K,我们将使用类型查询告诉编译器返回与传入字段的类型相同 (some.Api[K])
declare namespace some {
type Api = {
foo(n: number): string;
bar(s: string, n: number) : string
}
function api(): Api;
}
function wrapCallByName<K extends keyof some.Api>(functionName: K) : some.Api[K] {
return ((...args: any[]) => {
try {
return (some.api()[functionName] as any)(...args);
} catch (error) {
throw error;
}
}) as any;
}
const foo = wrapCallByName('foo')
foo(1) //returns string
const bar = wrapCallByName('bar')
bar('', 1) //returns string
Playground
正如你所看到的,上面的实现有很多类型断言。这是因为有几个问题。首先,对 api 的索引访问将导致 API 中所有字段的不可调用联合。其次,我们返回的函数与 api 的任何字段都不兼容。为了解决这个问题,我们可以使用额外的间接方法,使编译器将对象的值视为具有签名(... a: any[]) =>any 的函数。这将消除对任何断言的需要。
declare namespace some {
type Api = {
foo(n: number): string;
bar(s: string, n: number): string
}
function api(): Api;
}
function wrapBuilder<T extends Record<keyof T, (...a: any[]) => any>>(fn: () => T) {
return function <K extends keyof T>(functionName: K): T[K] {
return ((...args: any[]) => {
try {
return fn()[functionName](...args);
} catch (error) {
throw error;
}
});
}
}
const wrapCallByName = wrapBuilder(() => some.api());
const foo = wrapCallByName('foo');
foo(1); //returns string
const bar = wrapCallByName('bar');
bar('', 1); //returns string
Playground
我提到了这个不错的,没有断言的实现,因为您的问题明确拒绝了它,我个人对第一个版本感到满意,特别是如果您的 api 仅公开功能。包装函数只需要编写一次,我想不出断言会导致问题的情况,更重要的部分是公共签名正确转发类型。