如何在 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

Playground

如您所见,上面的实现对 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

Playground

我提到这个很好的,没有断言的实现,因为你的问题特别否定了它,就我个人而言,我会对第一个版本感到满意,特别是如果你 api 只公开功能。包装函数只需要编写一次,我想不出断言会导致问题的情况,更重要的部分是 public 签名正确转发类型。