如何在 TypeScript 中键入按名称包装另一个函数的函数
How to type a function that wraps another function by name in TypeScript
这是我目前的情况:
function wrapCallByName<T extends any[], R>(functionName: keyof some.api.Api) {
return (...args: T) => {
try {
some.api()[functionName](...args);
} catch (error) {
myHandleApiError(error);
}
}
}
它说 some.api()[functionName]
可能是 undefined
并且它不知道它的参数是什么类型。
但由于 functionName
的类型,它不能(即可能不会)是 undefined
,我们知道这些类型是什么。
理想情况下,return 类型的 wrapCallByName 是 some.api()[functionName] 的函数签名。
有没有办法在 TypeScript 中正确输入它?
这个问题有两个部分,第一部分是正确地获得函数的 public 签名。我们希望函数接收 some.Api
的键和 return 与原始键相同类型的值。为此,我们需要一个扩展 keyof some.Api
的额外类型参数 K
,我们将使用类型查询告诉编译器 return 与传入字段的类型相同( 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
如您所见,上面的实现对 any 有很多类型断言。这是因为有几个问题。首先,对 api 的索引访问将导致 API 中所有字段的不可调用联合。其次,我们 return 的函数与 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
我提到这个很好的,没有断言的实现,因为你的问题特别否定了它,就我个人而言,我会对第一个版本感到满意,特别是如果你 api 只公开功能。包装函数只需要编写一次,我想不出断言会导致问题的情况,更重要的部分是 public 签名正确转发类型。
这是我目前的情况:
function wrapCallByName<T extends any[], R>(functionName: keyof some.api.Api) {
return (...args: T) => {
try {
some.api()[functionName](...args);
} catch (error) {
myHandleApiError(error);
}
}
}
它说 some.api()[functionName]
可能是 undefined
并且它不知道它的参数是什么类型。
但由于 functionName
的类型,它不能(即可能不会)是 undefined
,我们知道这些类型是什么。
理想情况下,return 类型的 wrapCallByName 是 some.api()[functionName] 的函数签名。
有没有办法在 TypeScript 中正确输入它?
这个问题有两个部分,第一部分是正确地获得函数的 public 签名。我们希望函数接收 some.Api
的键和 return 与原始键相同类型的值。为此,我们需要一个扩展 keyof some.Api
的额外类型参数 K
,我们将使用类型查询告诉编译器 return 与传入字段的类型相同( 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
如您所见,上面的实现对 any 有很多类型断言。这是因为有几个问题。首先,对 api 的索引访问将导致 API 中所有字段的不可调用联合。其次,我们 return 的函数与 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
我提到这个很好的,没有断言的实现,因为你的问题特别否定了它,就我个人而言,我会对第一个版本感到满意,特别是如果你 api 只公开功能。包装函数只需要编写一次,我想不出断言会导致问题的情况,更重要的部分是 public 签名正确转发类型。