typescript - 如何正确键入对象方法的包装函数?

标签 typescript typescript-generics

我有一个具有多个方法的对象,每个方法都有不同的参数,我正在尝试定义一个包装函数,该函数将接受来自该对象的方法并返回具有相同签名的函数。

Here是来自 TypeScript Playground 的示例。

interface myFunctions{
    one: () => number;
    two: () => number;
    echo: (str: string) => string;
}

const myFunctions: myFunctions = {
    one: () => 1,
    two: () => 2,
    echo: (str: string) => str
}

这是我当前的包装器定义:

const wrapper = <T extends keyof myFunctions> (
    func: myFunctions[T]
) => (...args: Parameters<myFunctions[T]>) => func(...args)

这是我想如何使用包装器的示例:

wrapper<'one'>(myFunctions.one)()
wrapper<'echo'>(myFunctions.echo)('hello')

Typescript 不接受我对包装器的定义,如 Parameters<myFunctions[T]>可以是空数组或参数str ,以及函数 echo需要一个字符串。

我对 Typescript 不是很精通,翻阅了 Typescript 手册,但找不到答案。关于 StackOverflow 中包装器的类似问题也不是我想要的。

定义 wrapper 的正确方法是什么? ?

最佳答案

每次开始输入内容时,问问自己:“如果我是编译器,我会期望什么?”。如果您分析包装函数的问题,您会注意到您需要让编译器知道:

  1. func 参数满足 (...args: any[]) => any 约束(即“任意函数”)。
  2. args 必须推断剩余参数(您使用 Parameters 实用程序类型解决)。
  3. 还必须推断结果函数的返回类型(您错过了这个)。它可以通过 ReturnType 实用程序类型轻松实现。

您不必告诉编译器,关键是什么 - 它足够智能,可以根据使用情况推断出函数的类型。现在,你如何重写你的类型?结合步骤 1 到 3,您将得到以下结果:

const wrapper = <U extends (...args: any[]) => any>(func: U) => (...args: Parameters<U>) : ReturnType<U> => func(...args);

const one = wrapper(myFunctions.one);   //() => number
const two = wrapper(myFunctions.two);   //() => number
const echo = wrapper(myFunctions.echo); //(str: string) => string

生成的泛型不限于您的 myFunctions 类型:

const rand = wrapper((a:string,b:number,c:boolean) => {}); //(a: string, b: number, c: boolean) => void

如果您只需要接受 myFunctions 类型的函数,只需让编译器知道 U 必须与 myFunctions 中的签名之一完全匹配> 成员。

要实现这一点,您需要保证类型“相同”。这可以通过双重条件类型来完成。它的一般形式如下所示:

A extends B ? B extends A ? C : never : never;

让我们将这个想法应用到用例中并创建一个辅助类型:

type IsOneOf<T,F> = { [ P in keyof T ] : F extends T[P] ? T[P] extends F ? T[P] : never : never; }[keyof T];

如果 T 没有匹配的成员,则上述内容将解析为 never,否则解析为匹配的成员。由于没有任何内容与 never 兼容,因此我们得到了所需的行为:

type IsOneOf<T,F> = { [ P in keyof T ] : F extends T[P] ? T[P] extends F ? T[P] : never : never; }[keyof T];

const wrapper = <U extends (...args: any[]) => any>(func: U extends IsOneOf<myFunctions, U> ? U : never) => (...args: Parameters<U>) : ReturnType<U> => func(...args);

const one = wrapper(myFunctions.one);
const two = wrapper(myFunctions.two);
const echo = wrapper(myFunctions.echo);
const rand = wrapper((a:string,b:number,c:boolean) => {}); //not assignable to parameter of type 'never'.
const hm = wrapper(() => 'a'); //also error

Playground

关于typescript - 如何正确键入对象方法的包装函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66199621/

相关文章:

javascript - Testcafe Applitools typescript - 错误 : Property 'accessibilityValidation' is missing in type

javascript - 简单测试但定位器无效 :-(

Angular 2 Activatedroute 参数在服务或外部 <router-outlet> 中不起作用

angular - 我想在我的 Angular Web 应用程序中使用 Azure 应用服务 'Application settings' 变量

asp.net - Typescript 2.7 中的严格类初始化

typescript - 接受特定类型对象的 keyof 吗?

typescript - 了解 TS 条件类型

Typescript 保持对象上的类型信息映射

typescript - 重载签名、联合类型和 "No overload matches this call"错误

Typescript - 类型安全深度省略,或 : how to list legal object paths