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



假设我们有一个类:

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的类型?我对如何解决这个问题完全感到困惑。

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作为第一个参数的类型。这是因为

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

您可以在我的文章第一部分找到更多信息。这是因为TS不知道您到底在使用哪个methodName。因此,TS编译器与方法中的所有参数相交:string & 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

游乐场

如果你对函数自变量推理感兴趣,你可以查看我的博客

也可以使用条件语句进行类型缩小(适用于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

但这没有多大意义。

您可以传递一个扩展keyof Foo的泛型密钥类型,以在Foo中查找方法,然后获得正确的方法签名,如下所示:

type ArgsOf<F> = F extends (...args: infer A) => void ? A : never;
const foo = new Foo();
function runCommand<K extends keyof Foo>(name: K, ...args: ArgsOf<Foo[K]>) {
return (foo[name] as any)(...args);
}

最新更新