2 votes

Typescript obtenir le type des membres seulement appelables/exécutables

J'ai une classe avec des types de membres mixtes, des champs et des méthodes (par exemple MyClass ci-dessous). J'essaie d'avoir une commande créée qui appelle une fonction et est capable de l'exécuter tout en conservant le contexte this.

Le code ci-dessous fonctionne si j'utilise une classe qui n'a pas de champs en elle, seulement des membres appelables (par exemple, si je commente public field: number = 1;) Cela fonctionne bien, bon sens du type, erreurs si la méthode de la classe n'existe pas, erreurs si les paramètres sont mauvais. mais il semble ne pas apprécier lorsque je passe une classe qui a des membres non appelables. Peut-être que vous avez une solution pour cela?

Exemple de terrain de jeu TS

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 Function ? T[M] : never;

interface ICommand{

    execute(): any;
}

 class Command implements ICommand{
    api: MyClass;

    constructor(
        api: MyClass, 
        private func: TypeOfClassMethod, 
        private args:  Parameters> //
        ) {
        this.api = api;
    }

    execute():  ReturnType>{ 
 // error[1] type ‘TypeOfClassMethod’ 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, [] );

MODIFIER : Mon principal problème avec le code que j'avais est qu'il génère des erreurs si j'ai des membres non-fonction dans une classe, je veux que cela fonctionne pour d'autres classes qui ont des membres non exécutables mais me donnent une erreur si je veux créer une commande avec un membre qui n'est pas exécutable.

En gros, je veux appeler cela dans une fonction de wrapper callWithRetry à laquelle est passée une commande et est exécutée jusqu'à ce qu'elle réussisse ou qu'un certain critère d'arrêt soit atteint.

Jetez un œil ici, par exemple (terrain de jeu TS)

Ou celui-ci qui est basé sur la deuxième option de votre première réponse

Les deux manquent de type de retour lorsque j'essaie de l'appeler à l'intérieur d'une fonction.

function executeCommand(command: Command) {
 // je veux l'utiliser de manière similaire à cela, dans un mécanisme de réessai, mais dans ce cas je perds le returnType
  return command.execute()
}

const result = executeCommand(command); // le résultat aura un type de retour de any

MISE À JOUR : Je l'ai fait fonctionner comme je le voulais, merci beaucoup pour l'aide! voici la solution dans TS playground

2voto

Serhii Bilyk Points 9

Explication/description dans les commentaires

class MyClass {
  constructor() { }

  public field: number = 1;

  public printMessage(): void {
    console.log("message")
  }
  public printNumber(i: number): number {
    return i;
  }
}

// Type de base pour toute fonction/méthode
type Fn = (...args: any) => any

// Obtenir l'union de toutes les valeurs
type Values = T[keyof T]

/**
 * Convertit un objet en dictionnaire où
 *  les clés sont simplement les clés de l'objet
 *  les valeurs sont des tuples où
 * 
 *    le premier élément est la valeur de la clé
 *    le deuxième élément est un tableau d'arguments
 * 
 *      si le premier élément n'est pas une méthode,
 *      le deuxième élément sera un tableau vide
 */
type ObtainMethods = {
  [Prop in keyof T]: T[Prop] extends Fn ? [T[Prop], Parameters] : [T[Prop], []]
}
{
  //   type Test = {
  //     field: [number, []];
  //     printMessage: [() => void, []];
  //     printNumber: [(i: number) => number, [i: number]];
  // }
  type Test = ObtainMethods
}

/**
 * Params obtient une union de toutes les valeurs de l'objet
 * Comme nous avons une union de tuples, nous pouvons l'utiliser
 * pour typer les paramètres restants
 */
type Params = Values>
{
  // | [[number, []], []] 
  // | [[() => void, []], []]
  // | [[(i: number) => number, [i: number]], []]
  type Test = Params>
}

interface ICommand {
  execute(): any;
}

class Command{
  api: Instance;
  args: Params

  constructor(
    api: Instance,
    ...args: Params
  ) {
    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

Aire de jeu

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X