通过 Typescript 中的方法名称获取 return 类型的 class 方法
Get return type of class method via method name in Typescript
假设我们有一个 class:
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
作为第一个参数的类型。这是因为
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.
您可以在我的 article 的第一部分中找到更多信息。这是因为 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
如果你对函数参数推断感兴趣,你可以查看我的 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
但意义不大
假设我们有一个 class:
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
作为第一个参数的类型。这是因为
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred.
您可以在我的 article 的第一部分中找到更多信息。这是因为 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
如果你对函数参数推断感兴趣,你可以查看我的 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
但意义不大