如何在TypeScript中键入按名称包装另一个函数的函数

时间:2018-08-26 01:59:01

标签: typescript typescript-generics

这是我到目前为止所拥有的:

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,而且它不知道其参数是什么类型。

但是由于undefined的类型,它不可能(也可能不会)是functionName,我们确实知道类型将是什么。

理想情况下,wrapCallByName的返回类型是some.api()[functionName]的函数签名。

是否可以在TypeScript中正确键入此内容?

1 个答案:

答案 0 :(得分:1)

此问题有两个部分,第一部分是正确获取函数的公共签名。我们希望函数接受some.Api的键并返回与原始键相同类型的值。为此,我们将需要一个扩展了K的附加类型参数keyof some.Api,并且我们将使用类型查询来告诉编译器返回值与传入字段的类型相同({{1} })

some.Api[K]

Playground

如您所见,以上实现对任何类型都有很多类型断言。这是因为有几个问题。首先,对api的索引访问将导致API中所有字段的不可调用的并集。其次,我们返回的函数与api的任何字段都不兼容。为了解决这个问题,我们可以使用一种额外的间接方式,使编译器将对象的值视为具有签名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 的函数。这将消除对任何断言的需求。

(... a: any[]) =>any

Playground

我提到了这个不错的,没有断言的实现,因为您的问题专门否认了它,就我个人而言,我对第一个版本足够满意,尤其是如果api仅公开函数时。包装函数只需要编写一次,我想不出断言会引起问题的情况,更重要的是公共签名正确地转发类型。