我有一个混合成员类型,字段和方法的类(例如下面的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
游乐场