Typescript 中通过方法名称获取类方法的返回类型

Pao*_*der 0 javascript higher-order-functions typescript function-definition

假设我们有一堂课:

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

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

  world(request: C): Promise<D> {  }
}
Run Code Online (Sandbox Code Playgroud)

我想实现执行以下实例方法的函数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>.
Run Code Online (Sandbox Code Playgroud)

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

cap*_*ian 8

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>
Run Code Online (Sandbox Code Playgroud)

我们来测试一下:


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

executeFoo('hello') // ok
executeFoo('world') // ok
executeFoo('var1') // expected error
Run Code Online (Sandbox Code Playgroud)

然而,第二个参数有一个问题:

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)
}
Run Code Online (Sandbox Code Playgroud)

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

Argument of type 'string | number' is not assignable to parameter of type 'never'. 
Type 'string' is not assignable to type 'never'.
Run Code Online (Sandbox Code Playgroud)

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

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

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

因此,您在方法中期望什么类型的参数非常重要。

如何修复它?

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

Argument of type 'string | number' is not assignable to parameter of type 'never'. 
Type 'string' is not assignable to type 'never'.
Run Code Online (Sandbox Code Playgroud)

操场

如果您对函数参数推理感兴趣,可以查看我的博客

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


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
Run Code Online (Sandbox Code Playgroud)

但这没有多大意义。