Definieren des TypeScript-Rückruftyps

172

Ich habe die folgende Klasse in TypeScript:

class CallbackTest
{
    public myCallback;

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

Ich benutze die Klasse wie folgt:

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

Der Code funktioniert und zeigt erwartungsgemäß ein Meldungsfeld an.

Meine Frage ist: Gibt es einen Typ, den ich für mein Klassenfeld bereitstellen kann myCallback? Im Moment ist das öffentliche Feld myCallbackvom Typ anywie oben gezeigt. Wie kann ich die Methodensignatur des Rückrufs definieren? Oder kann ich den Typ einfach auf einen Rückruftyp einstellen? Oder kann ich beides tun? Muss ich verwenden any(implizit / explizit)?

Ich habe so etwas versucht, aber es hat nicht funktioniert (Fehler beim Kompilieren):

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

Ich konnte online keine Erklärung dafür finden, also hoffe ich, dass Sie mir helfen können.

Nikeee
quelle

Antworten:

211

Ich habe gerade etwas in der TypeScript-Sprachspezifikation gefunden, es ist ziemlich einfach. Ich war ziemlich nah dran.

Die Syntax lautet wie folgt:

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

In meinem Beispiel wäre es

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

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}
Nikeee
quelle
8
Ich verstehe nicht, warum der Parametername erforderlich ist, um die Rückrufsignatur zu definieren ...
2grit
4
Ich denke, es könnte ein kulturelles Erbe des C #
-Teams sein
@nikeee können Sie einen Link zu dieser Seite der Dokumentation bereitstellen?
JCairney
Dies kann ein guter Link sein fettblog.eu/typescript-substitutability
nilakantha singh deo
147

Um noch einen Schritt weiter zu gehen, können Sie einen Typzeiger auf eine Funktionssignatur wie folgt deklarieren:

interface myCallbackType { (myArgument: string): void }

und benutze es so:

public myCallback : myCallbackType;
Leng
quelle
9
Dies ist (IMO) eine viel bessere Lösung als die akzeptierte Antwort, da Sie damit einen Typ definieren und dann beispielsweise einen Parameter dieses Typs (den Rückruf) übergeben können, den Sie dann beliebig verwenden können, einschließlich des Aufrufs. Die akzeptierte Antwort verwendet eine Mitgliedsvariable, und Sie müssen die Mitgliedsvariable auf Ihre Funktion setzen und dann eine Methode aufrufen - hässlich und fehleranfällig, da das Festlegen der Variablen zuerst Teil des Vertrags zum Aufrufen der Methode ist.
David
Sie können den Rückruf auch einfach als let callback: myCallbackType|null = null;
nullwert festlegen
1
Beachten Sie, dass TSLint sich beschweren würde "TSLint: Schnittstelle hat nur eine Anrufsignatur - verwenden Sie type MyHandler = (myArgument: string) => voidstattdessen. (Aufrufbare Typen)" ; siehe TSVs Antwort
Arjan
Der frühere Entwurf dieser Antwort löste tatsächlich das Problem, das mich zu dieser Frage führte. Ich hatte versucht, eine ausreichend zulässige Funktionssignatur innerhalb einer Schnittstelle zu definieren, die eine beliebige Anzahl von Parametern akzeptieren konnte, ohne einen Compilerfehler zu erzeugen. Die Antwort in meinem Fall war zu verwenden ...args: any[]. Beispiel: Exportschnittstelle MyInterface {/ ** Eine Rückruffunktion. / callback: (... args: any []) => any, / * Parameter für die Rückruffunktion. * / callbackParams: any []}
Ken Lyon
61

Sie können einen neuen Typ deklarieren:

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

var handler: MyHandler;

Aktualisieren.

Das declareSchlüsselwort ist nicht erforderlich. Es sollte in den .d.ts-Dateien oder in ähnlichen Fällen verwendet werden.

TSV
quelle
Wo finde ich die Dokumentation dazu?
E. Sundin
@ E. Sundin - Abschnitt "Typ Aliase" der typescriptlang.org/docs/handbook/advanced-types.html
TSV
1
Obwohl dies wahr und gut zu wissen ist, heißt es auf derselben Seite (heutzutage) auch: "Da eine ideale Eigenschaft von Software für Erweiterungen offen ist, sollten Sie nach Möglichkeit immer eine Schnittstelle über einen Typalias verwenden."
Arjan
@Arjan - Ich bin damit völlig einverstanden für Objekte. Könnten Sie bitte angeben, wie Sie eine Funktion erweitern möchten?
TSV
Beachten Sie, dass die Typdeklaration optional ist: var handler: (myArgument: string) => voidsyntaktisch gültig (wenn etwas chaotisch).
Hutch
35

Hier ist ein Beispiel: Akzeptieren Sie keine Parameter und geben Sie nichts zurück.

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();

Wenn Sie einen Parameter akzeptieren möchten, können Sie diesen auch hinzufügen:

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

Und wenn Sie einen Wert zurückgeben möchten, können Sie diesen auch hinzufügen:

public myCallback: {(msg: string): number;};
Fenton
quelle
Funktionell sind sie identisch - sie definieren dasselbe und ermöglichen Ihnen die Typprüfung der Funktionssignatur. Sie können verwenden, was Sie bevorzugen. Die Spezifikation sagt, dass sie sind exactly equivalent.
Fenton
6
@nikeee: Die Frage ist eher, was ist anders mit deiner Antwort? Steve hat seine Antwort vor Ihrer gepostet.
Jgauffin
@jgauffin In der Tat ist das Ergebnis das gleiche. IMO ist die von mir veröffentlichte Lösung natürlicher, wenn es um Rückrufe geht, da Steves Version vollständige Schnittstellendefinitionen zulässt. Es hängt von Ihrer Präferenz ab.
Nikeee
@Fenton Könnten Sie bitte einen Link zu dieser Dokumentation bereitstellen?
JCairney
17

Wenn Sie eine generische Funktion wünschen, können Sie Folgendes verwenden. Obwohl es nirgendwo dokumentiert zu sein scheint.

class CallbackTest {
  myCallback: Function;
}   
Aschblau
quelle
3

Sie können Folgendes verwenden:

  1. Typ Alias ​​(mit typeSchlüsselwort, Aliasing eines Funktionsliteral)
  2. Schnittstelle
  3. Funktionsliteral

Hier ist ein Beispiel für deren Verwendung:

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
quelle
1

Beim Versuch, den Rückruf einem Ereignis-Listener hinzuzufügen, ist derselbe Fehler aufgetreten. Seltsamerweise wurde das Problem behoben, indem der Rückruftyp auf EventListener gesetzt wurde. Es sieht eleganter aus, als eine ganze Funktionssignatur als Typ zu definieren, aber ich bin mir nicht sicher, ob dies der richtige Weg ist.

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
quelle
mag ich. Aber wo ist die andere Klasse in Aktion?
Yaro
1

Ich bin etwas spät dran, aber seit einiger Zeit können Sie in TypeScript die Art des Rückrufs mit definieren

type MyCallback = (KeyboardEvent) => void;

Anwendungsbeispiel:

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

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