【问题标题】:Get return type of class method via method name in Typescript通过 Typescript 中的方法名称获取类方法的返回类型
【发布时间】:2022-01-10 12:22:57
【问题描述】:

假设我们有一个类:

class Foo {
  var1: string = 'var1';
  var2: string = 'var2';

  hello(request: A): Promise<B> {  }

  world(request: C): Promise<D> {  }
}

我要实现执行Foo实例方法的函数:

const foo = new Foo();
const executeFoo = (methodName: string, firstParam: any) => { // <- I'm stuck in this arrow function.
  return foo[methodName](firstParam);
};

executeFoo('hello', testParam); // testParams is type of A, then return type should Promise<B>.
executeFoo('world', testParam2); // testParams2 is type of C, then return type should Promise<D>.

有没有办法定义executeFoo的类型?我完全不知道如何解决这个问题。

【问题讨论】:

  • 你的代码语法不正确,你知道吗?
  • @Paosder 考虑发布没有语法错误的可重现示例。在发布之前尝试将您的代码粘贴到 TS 游乐场。它会让事情变得更容易
  • @DimaParzhitsky 是的,那是一些伪代码,但我发现 executeFoo 的语法完全错误。感谢您指出。

标签: javascript typescript higher-order-functions function-definition


【解决方案1】:

Afaik,没有安全方法可以在不改变函数体或使用类型断言的情况下做你想做的事。

为了验证函数参数,首先我们需要从Foo获取所有方法键:

class Foo {
    var1: string = 'var1';
    var2: string = 'var2';

    hello(request: string) { }

    world(request: number) { }
}

// This type reflects any function/method
type Fn = (...args: any[]) => any

type ObtainMethods<T> = {
    [Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}[keyof T]


//  "hello" | "world"
type AllowedMethods = ObtainMethods<Foo>

让我们测试一下:


const executeFoo = <Method extends ObtainMethods<Foo>>(
    methodName: Method
) => { }

executeFoo('hello') // ok
executeFoo('world') // ok
executeFoo('var1') // expected error

但是,第二个参数有问题:

const executeFoo = <Method extends ObtainMethods<Foo>>(
    methodName: Method, parameter: Parameters<Foo[Method]>[0]
) => {
    // Argument of type 'string | number' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'.
    foo[methodName](parameter)
}

您可能已经注意到,有一个错误。

Argument of type 'string | number' is not assignable to parameter of type 'never'. 
Type 'string' is not assignable to type 'never'.

这很重要。如果您尝试调用foo[methodName](),您将看到此函数需要never 作为第一个参数的类型。这是因为

同样,同一类型变量在逆变位置的多个候选会导致推断出交集类型。

您可以在我的第一部分article 中找到更多信息。这是因为 TS 不知道您使用的是哪个 methodName。因此,TS 编译器与方法中的所有参数相交:string &amp; number,因为这是使函数签名安全的唯一安全方法。

所以,您希望在方法中使用什么类型的参数非常重要。

如何解决?

在这个特定的例子中,我相信使用type assertion 是合理的:


const executeFoo = <Method extends ObtainMethods<Foo>>(
    methodName: Method, parameter: Parameters<Foo[Method]>[0]
) => {
    (foo[methodName] as (arg: Parameters<Foo[Method]>[0]) => void)(parameter)
}

executeFoo('hello', 'str') // ok
executeFoo('world', 42) // ok
executeFoo('world', "42") // expected error
executeFoo('var1') // expected error

Playground

如果你对函数参数推断感兴趣,可以查看我的blog

也可以使用条件语句进行类型缩小(适用于 TS >= 4.6)

type Fn = (...args: any[]) => any

type ObtainMethods<T> = {
    [Prop in keyof T]: T[Prop] extends Fn ? Prop : never
}[keyof T]


//  "hello" | "world"
type AllowedMethods = ObtainMethods<Foo>

type Values<T> = T[keyof T]

type AllowedArguments = {
    [Method in AllowedMethods]: [Method, Parameters<Foo[Method]>[0]]
}

const foo = new Foo();

const executeFoo = (
    ...[name, arg]: Values<AllowedArguments>
) => {
    if (name === 'hello') {
        foo[name](arg)
    } else {
        foo[name](arg)
    }
}

executeFoo('hello', 'str') // ok
executeFoo('world', 42) // ok
executeFoo('world', "42") // expected error
executeFoo('var1') // expected error

但这并没有多大意义。

【讨论】:

  • 好的。现在我通过你的详细解释完全理解了。谢谢!
猜你喜欢
  • 2016-11-02
  • 2019-04-16
  • 1970-01-01
  • 2021-04-05
  • 1970-01-01
  • 2013-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多