Typescript只能获得可调用/可执行成员的类型



我有一个混合成员类型,字段和方法的类(例如下面的MyClass)。我试图有一个命令创建调用一个函数,并能够执行它,同时保持this上下文。

下面的代码工作,如果我使用一个没有字段的类,只有可调用的成员(例如,如果我注释出public field: number = 1;)这工作得很好,良好的类型感,错误,如果类方法不存在,错误如果坏的参数。但它不喜欢我传递一个有不可调用成员的类。也许你们有解决办法?

TS playground示例

class MyClass{
constructor() {}

public field: number = 1;
public printMessage(): void{
console.log("message")
}
public printNumber(i: number): number{
return i;
}
}
type TypeOfClassMethod<T, M extends keyof T> = T[M] extends Function ? T[M] : never;
interface ICommand{
execute(): any;
}
class Command<F extends keyof MyClass> implements ICommand{
api: MyClass;
constructor(
api: MyClass, 
private func: TypeOfClassMethod<MyClass, F>, 
private args:  Parameters<TypeOfClassMethod<MyClass, F>> //
) {
this.api = api;
}

execute():  ReturnType<TypeOfClassMethod<MyClass, F>>{ 
// error[1] type ‘TypeOfClassMethod<MyClass, F>’ does not satisfy the constraint ‘(...args: any) => any’.
// Type ‘Function & MyClass[F]’ is not assignable to type ‘(...args: any) => any’.
return this.func.call(this.api, ...this.args)
}
}

let instance = new MyClass();
const command = new Command<"printNumber">(instance, instance.printNumber, [5] );
const wrongCOmmand = new Command<"field">(instance, instance.field, [] );

编辑:我对实现的主要问题是,如果我在一个类中有非功能成员,它会给出错误,我希望它适用于其他具有不可执行成员的类,但如果我想创建一个具有不可执行成员的命令,则会给我一个错误。

基本上,我想在包装器函数callWithRetry中调用它,该函数传递命令并执行它,直到它成功或满足特定的中断标准。

看这里,作为一个例子(TS playground)

或者这个是基于你第一个答案的第二个选项

当我试图在函数中调用它时,它们都缺少返回类型。

function executeCommand < InstanceType > (command: Command < InstanceType > ) {
// i want to use this in a manner similar to this, in a retry mechanism, but in this case I lose the returnType
return command.execute()
}
const result = executeCommand(command); // result will have a return type of any

更新:我让它按照我想要的方式工作,非常感谢你的帮助!这是TS playground

的解决方案

说明/描述在注释中

class MyClass {
constructor() { }
public field: number = 1;
public printMessage(): void {
console.log("message")
}
public printNumber(i: number): number {
return i;
}
}
// Base type for any function/method
type Fn = (...args: any) => any
// Obtain union of all values
type Values<T> = T[keyof T]
/**
* Converts object to dictionary where
*  keys are just object keys
*  values are a tuples where 
* 
*    first element is key value
*    second element is an array of arguments
* 
*      if first element is not a method,
*      second element will be empty array
*/
type ObtainMethods<T> = {
[Prop in keyof T]: T[Prop] extends Fn ? [T[Prop], Parameters<T[Prop]>] : [T[Prop], []]
}
{
//   type Test = {
//     field: [number, []];
//     printMessage: [() => void, []];
//     printNumber: [(i: number) => number, [i: number]];
// }
type Test = ObtainMethods<MyClass>
}
/**
* Params obtains a union of all object values
* Since we have a union of tuples, we can use it
* for typing rest parameters
*/
type Params<T> = Values<ObtainMethods<T>>
{
// | [[number, []], []] 
// | [[() => void, []], []]
// | [[(i: number) => number, [i: number]], []]
type Test = Params<ObtainMethods<MyClass>>
}
interface ICommand {
execute(): any;
}

class Command<Instance>{
api: Instance;
args: Params<Instance>
constructor(
api: Instance,
...args: Params<Instance>
) {
this.api = api;
this.args = args
}
execute() {
if (this.args[0] instanceof Function) {
return this.args[0].call(this.api, ...this.args)
}
}
}

let instance = new MyClass();
const command = new Command(instance, instance.printNumber, [5]); // ok
const command2 = new Command(instance, instance.printNumber, ['str']); // expected error
const wrongCOmmand1 = new Command(instance, instance.field, []); // ok
const wrongCOmmand2 = new Command(instance, instance.field, []); // ok

游乐场

最新更新