打字稿仅获取 callable/executable 成员的类型
Typescript get type of only callable/executable members
我有一个包含混合成员类型、字段和方法的 class(例如下面的 MyClass
)。
我正在尝试创建一个调用函数并能够在保持 this
上下文的同时执行它的命令。
如果我使用 class 其中没有字段,只有可调用成员(例如,如果我注释掉 public field: number = 1;
),下面的代码可以正常工作,良好的类型感,错误如果 class 方法不存在,如果参数错误则出错。但是当我传递具有不可调用成员的 class 时,它有点不喜欢它。
也许你们有解决方案?
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, [] );
编辑:
我的实现的主要问题是如果我在 class 中有非函数成员,它会出错如果我想使用不可执行的成员创建命令,则会出错。
基本上我想在一个包装函数中调用它 callWithRetry
它会传递一个命令并执行它直到成功或满足特定的中断条件。
Take a look here, as an example (TS playground)
Or this one which is based on your first answer's second option
当我尝试在函数中调用它时,两者都缺少 return 类型。
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
更新:
我让它按照我想要的方式工作,非常感谢你的帮助!
here is the solution in TS playground
Explanation/description 在评论中
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
我有一个包含混合成员类型、字段和方法的 class(例如下面的 MyClass
)。
我正在尝试创建一个调用函数并能够在保持 this
上下文的同时执行它的命令。
如果我使用 class 其中没有字段,只有可调用成员(例如,如果我注释掉 public field: number = 1;
),下面的代码可以正常工作,良好的类型感,错误如果 class 方法不存在,如果参数错误则出错。但是当我传递具有不可调用成员的 class 时,它有点不喜欢它。
也许你们有解决方案?
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, [] );
编辑: 我的实现的主要问题是如果我在 class 中有非函数成员,它会出错如果我想使用不可执行的成员创建命令,则会出错。
基本上我想在一个包装函数中调用它 callWithRetry
它会传递一个命令并执行它直到成功或满足特定的中断条件。
Take a look here, as an example (TS playground)
Or this one which is based on your first answer's second option
当我尝试在函数中调用它时,两者都缺少 return 类型。
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
更新: 我让它按照我想要的方式工作,非常感谢你的帮助! here is the solution in TS playground
Explanation/description 在评论中
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