Définition du type de rappel TypeScript

173

J'ai la classe suivante dans TypeScript:

class CallbackTest
{
    public myCallback;

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}

J'utilise la classe comme ceci:

var test = new CallbackTest();
test.myCallback = () => alert("done");
test.doWork();

Le code fonctionne, il affiche donc une boîte de message comme prévu.

Ma question est la suivante: y a-t-il un type que je peux fournir pour mon champ de classe myCallback? À l'heure actuelle, le champ public myCallbackest du type anyindiqué ci-dessus. Comment puis-je définir la signature de méthode du callback? Ou puis-je simplement définir le type sur une sorte de type de rappel? Ou puis-je faire rien de tout cela? Dois-je utiliser any(implicite / explicite)?

J'ai essayé quelque chose comme ça, mais cela n'a pas fonctionné (erreur de compilation):

public myCallback: ();
// or:
public myCallback: function;

Je n'ai trouvé aucune explication à cela en ligne, alors j'espère que vous pourrez m'aider.

nikeee
la source

Réponses:

212

Je viens de trouver quelque chose dans la spécification du langage TypeScript, c'est assez simple. J'étais assez proche.

la syntaxe est la suivante:

public myCallback: (name: type) => returntype;

Dans mon exemple, ce serait

class CallbackTest
{
    public myCallback: () => void;

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}
nikeee
la source
8
Je ne comprends pas pourquoi le nom du paramètre est nécessaire pour définir la signature de rappel ...
2grit
4
Je suppose que cela pourrait être un héritage culturel de l'équipe C # , je pense que j'aime ça après tout ...
2grit
@nikeee pouvez-vous fournir un lien vers cette page de la documentation?
jcairney
Cela peut être un bon lien fettblog.eu/typescript-substitutability
nilakantha singh deo
148

Pour aller plus loin, vous pouvez déclarer un pointeur de type vers une signature de fonction comme:

interface myCallbackType { (myArgument: string): void }

et utilisez-le comme ceci:

public myCallback : myCallbackType;
Leng
la source
9
C'est (IMO) une bien meilleure solution que la réponse acceptée, car elle vous permet de définir un type, puis, par exemple, de passer un paramètre de ce type (le rappel) que vous pouvez ensuite utiliser comme vous le souhaitez, y compris en l'appelant. La réponse acceptée utilise une variable membre et vous devez définir la variable membre sur votre fonction, puis appeler une méthode - moche et sujette aux erreurs, car définir la variable en premier fait partie du contrat d'appel de la méthode.
David
Il vous permet également de définir facilement le rappel comme nullable, par exemplelet callback: myCallbackType|null = null;
Doches
1
Notez que TSLint se plaindrait "TSLint: L'interface n'a qu'une signature d'appel - utilisez type MyHandler = (myArgument: string) => voidplutôt. (Callable-types)" ; voir la réponse de TSV
Arjan
La première ébauche de cette réponse a en fait résolu le problème qui m'a conduit à cette question. J'avais essayé de définir une signature de fonction suffisamment permissive dans une interface qui pouvait accepter n'importe quel nombre de paramètres sans produire une erreur de compilation. La réponse dans mon cas était d'utiliser ...args: any[]. Exemple: interface d'exportation MyInterface {/ ** Une fonction de rappel. / callback: (... args: any []) => any, / * Paramètres de la fonction de rappel. * / callbackParams: any []}
Ken Lyon
62

Vous pouvez déclarer un nouveau type:

declare type MyHandler = (myArgument: string) => void;

var handler: MyHandler;

Mettre à jour.

Le declaremot-clé n'est pas nécessaire. Il doit être utilisé dans les fichiers .d.ts ou dans des cas similaires.

TSV
la source
Où puis-je trouver la documentation à ce sujet?
E. Sundin
@ E.Sundin - Section "Type Aliases" du typescriptlang.org/docs/handbook/advanced-types.html
TSV
1
Bien que cela soit vrai et agréable à savoir, la même page (de nos jours) indique également "Parce qu'une propriété idéale d'un logiciel est ouverte à l'extension, vous devriez toujours utiliser une interface sur un alias de type si possible."
Arjan
@Arjan - Je suis totalement d'accord avec cela pour les objets. Pouvez-vous préciser - comment souhaitez-vous étendre une fonction?
TSV
Notez que la déclaration de type est facultative: var handler: (myArgument: string) => voidest syntaxiquement valide (si un peu désordonnée).
Hutch
36

Voici un exemple - n'accepter aucun paramètre et ne rien renvoyer.

class CallbackTest
{
    public myCallback: {(): void;};

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}

var test = new CallbackTest();
test.myCallback = () => alert("done");
test.doWork();

Si vous souhaitez accepter un paramètre, vous pouvez également l'ajouter:

public myCallback: {(msg: string): void;};

Et si vous souhaitez renvoyer une valeur, vous pouvez également l'ajouter:

public myCallback: {(msg: string): number;};
Fenton
la source
Fonctionnellement, ils sont identiques - ils définissent la même chose et vous permettent de vérifier le type sur la signature de la fonction. Vous pouvez utiliser celui que vous préférez. La spécification dit qu'ils le sont exactly equivalent.
Fenton
6
@nikeee: La question est plutôt ce qui est différent avec votre réponse? Steve a posté sa réponse avant la vôtre.
jgauffin le
@jgauffin En effet, le résultat est le même. OMI, la solution que j'ai publiée est plus naturelle quand on parle de rappels, puisque la version de Steve permet des définitions d'interface complètes. Cela dépend de votre préférence.
nikeee
@Fenton pourriez-vous fournir un lien vers cette documentation s'il vous plaît?
jcairney
18

Si vous voulez une fonction générique, vous pouvez utiliser ce qui suit. Bien que cela ne semble pas être documenté nulle part.

class CallbackTest {
  myCallback: Function;
}   
Bleu cendre
la source
4

Vous pouvez utiliser les éléments suivants:

  1. Tapez Alias ​​(en utilisant type mot-clé, aliasing un littéral de fonction)
  2. Interface
  3. Fonction littérale

Voici un exemple de leur utilisation:

type myCallbackType = (arg1: string, arg2: boolean) => number;

interface myCallbackInterface { (arg1: string, arg2: boolean): number };

class CallbackTest
{
    // ...

    public myCallback2: myCallbackType;
    public myCallback3: myCallbackInterface;
    public myCallback1: (arg1: string, arg2: boolean) => number;

    // ...

}
Willem van der Veen
la source
2

Je suis tombé sur la même erreur en essayant d'ajouter le rappel à un écouteur d'événements. Étrangement, définir le type de rappel sur EventListener l'a résolu. Cela semble plus élégant que de définir une signature de fonction entière en tant que type, mais je ne suis pas sûr que ce soit la bonne façon de faire.

class driving {
    // the answer from this post - this works
    // private callback: () => void; 

    // this also works!
    private callback:EventListener;

    constructor(){
        this.callback = () => this.startJump();
        window.addEventListener("keydown", this.callback);
    }

    startJump():void {
        console.log("jump!");
        window.removeEventListener("keydown", this.callback);
    }
}
Kokodoko
la source
J'aime ça. Mais où est l'autre classe en action?
Yaro
2

Je suis un peu en retard, mais depuis quelque temps dans TypeScript vous pouvez définir le type de rappel avec

type MyCallback = (KeyboardEvent) => void;

Exemple d'utilisation:

this.addEvent(document, "keydown", (e) => {
    if (e.keyCode === 1) {
      e.preventDefault();
    }
});

addEvent(element, eventName, callback: MyCallback) {
    element.addEventListener(eventName, callback, false);
}
Daniel
la source