【问题标题】:How to type a function that wraps another function by name in TypeScript如何在 TypeScript 中键入一个按名称包装另一个函数的函数
【发布时间】:2018-08-26 19:38:57
【问题描述】:

这是我目前所拥有的:

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,而且我们确实知道类型是什么。

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

有没有办法在 TypeScript 中正确输入?

【问题讨论】:

标签: typescript typescript-generics


【解决方案1】:

这个问题有两个部分,首先是获取函数的公共签名权。我们希望函数接收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[]) =&gt;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 仅公开功能。包装函数只需要编写一次,我想不出断言会导致问题的情况,更重要的部分是公共签名正确转发类型。

【讨论】:

    猜你喜欢
    • 2013-05-23
    • 1970-01-01
    • 2018-07-27
    • 2021-10-12
    • 2021-03-26
    • 2019-03-01
    • 2018-07-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多