Definiowanie typu wywołania zwrotnego TypeScript

172

Mam następującą klasę w TypeScript:

class CallbackTest
{
    public myCallback;

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

Używam klasy w ten sposób:

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

Kod działa, więc wyświetla okno komunikatu zgodnie z oczekiwaniami.

Moje pytanie brzmi: Czy jest jakiś typ, który mogę podać w polu mojej klasy myCallback? Obecnie pole publiczne myCallbackjest typu anypokazanego powyżej. Jak mogę zdefiniować sygnaturę metody wywołania zwrotnego? Czy mogę po prostu ustawić typ na rodzaj oddzwaniania? Czy mogę nic z tego zrobić? Czy muszę używać any(niejawne / jawne)?

Próbowałem czegoś takiego, ale nie zadziałało (błąd kompilacji):

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

Nie mogłem znaleźć żadnego wyjaśnienia tego w Internecie, więc mam nadzieję, że możesz mi pomóc.

nikeee
źródło

Odpowiedzi:

211

Właśnie znalazłem coś w specyfikacji języka TypeScript, jest to dość łatwe. Byłem całkiem blisko.

składnia jest następująca:

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

W moim przykładzie byłoby to

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

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}
nikeee
źródło
8
Nie rozumiem, dlaczego nazwa parametru jest wymagana do zdefiniowania sygnatury wywołania zwrotnego ...
2grit
4
Myślę, że to może być dziedzictwo kulturowe zespołu C # , myślę, że mimo wszystko mi się podoba ...
2grit
@nikeee czy możesz podać link do tej strony dokumentacji?
jcairney,
Może to być link dobrą fettblog.eu/typescript-substitutability
nilakantha Singh deo
147

Aby pójść o krok dalej, możesz zadeklarować wskaźnik typu do sygnatury funkcji, na przykład:

interface myCallbackType { (myArgument: string): void }

i użyj go w ten sposób:

public myCallback : myCallbackType;
Leng
źródło
9
Jest to (IMO) znacznie lepsze rozwiązanie niż zaakceptowana odpowiedź, ponieważ pozwala zdefiniować typ, a następnie, powiedzmy, przekazać parametr tego typu (wywołanie zwrotne), którego możesz następnie użyć w dowolny sposób, w tym wywołanie go. Zaakceptowana odpowiedź używa zmiennej składowej i musisz ustawić zmienną składową na swoją funkcję, a następnie wywołać metodę - brzydką i podatną na błędy, ponieważ ustawienie zmiennej w pierwszej kolejności jest częścią kontraktu wywołania metody.
David
Pozwala także łatwo ustawić wywołanie zwrotne jako zerowe, np.let callback: myCallbackType|null = null;
Doches
1
Zauważ, że TSLint narzekałby "TSLint: Interfejs ma tylko sygnaturę wywołania - type MyHandler = (myArgument: string) => voidzamiast tego użyj . (Wywoływalne-typy)" ; zobacz odpowiedź TSV
Arjan
Wcześniejszy szkic tej odpowiedzi faktycznie rozwiązał problem, który doprowadził mnie do tego pytania. Próbowałem zdefiniować wystarczająco liberalny podpis funkcji w interfejsie, który mógłby akceptować dowolną liczbę parametrów bez powodowania błędu kompilatora. Odpowiedzią w moim przypadku było użycie ...args: any[]. Przykład: interfejs eksportu MyInterface {/ ** Funkcja zwrotna. / callback: (... args: any []) => any, / * Parametry funkcji zwrotnej. * / callbackParams: any []}
Ken Lyon
61

Możesz zadeklarować nowy typ:

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

var handler: MyHandler;

Aktualizacja.

Słowo declarekluczowe nie jest konieczne. Powinien być używany w plikach .d.ts lub w podobnych przypadkach.

TSV
źródło
Gdzie znajdę odpowiednią dokumentację?
E. Sundin
@ E.Sundin - Sekcja „Aliasy typów” na stronie typescriptlang.org/docs/handbook/advanced-types.html
TSV
1
Chociaż jest to prawda i miło wiedzieć, ta sama strona (obecnie) stwierdza również: „Ponieważ idealna właściwość oprogramowania jest otwarta na rozszerzenie, należy zawsze używać interfejsu zamiast aliasu typu, jeśli to możliwe”.
Arjan
@Arjan - całkowicie się z tym zgadzam w przypadku obiektów. Czy mógłbyś określić - w jaki sposób chcesz rozszerzyć funkcję?
TSV
Zauważ, że deklaracja typu jest opcjonalna: var handler: (myArgument: string) => voidjest poprawna składniowo (jeśli jest trochę niechlujna).
Hutch
35

Oto przykład - nie przyjmowanie żadnych parametrów i nic nie zwraca.

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

Jeśli chcesz zaakceptować parametr, możesz go również dodać:

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

A jeśli chcesz zwrócić wartość, możesz to również dodać:

public myCallback: {(msg: string): number;};
Fenton
źródło
Funkcjonalnie są identyczne - definiują to samo i umożliwiają sprawdzenie typu w sygnaturze funkcji. Możesz użyć tego, co wolisz. Specyfikacja mówi, że tak exactly equivalent.
Fenton
6
@nikeee: Pytanie brzmi raczej, co różni się od Twojej odpowiedzi? Steve opublikował swoją odpowiedź przed twoją.
jgauffin
@jgauffin Rzeczywiście, wynik jest taki sam. IMO rozwiązanie, które opublikowałem, jest bardziej naturalne, gdy mówię o wywołaniach zwrotnych, ponieważ wersja Steve'a pozwala na definicje całych interfejsów. To zależy od twoich preferencji.
nikeee
@Fenton, czy możesz podać link do tej dokumentacji?
jcairney,
17

Jeśli chcesz mieć funkcję ogólną, możesz użyć następujących. Chociaż wydaje się, że nie jest to nigdzie udokumentowane.

class CallbackTest {
  myCallback: Function;
}   
Popielaty niebieski
źródło
3

Możesz użyć następujących:

  1. Wpisz Alias ​​(używając typesłowa kluczowego, aliasując literał funkcji)
  2. Berło
  3. Literał funkcji

Oto przykład, jak z nich korzystać:

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
źródło
1

Napotkałem ten sam błąd podczas próby dodania wywołania zwrotnego do detektora zdarzeń. O dziwo, ustawienie typu wywołania zwrotnego na EventListener rozwiązało problem. Wygląda to bardziej elegancko niż definiowanie całego podpisu funkcji jako typu, ale nie jestem pewien, czy jest to właściwy sposób.

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
źródło
lubię to. Ale gdzie jest w akcji druga klasa?
Yaro
1

Trochę się spóźniłem, ale od jakiegoś czasu w TypeScript można zdefiniować typ wywołania zwrotnego za pomocą

type MyCallback = (KeyboardEvent) => void;

Przykład użycia:

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

addEvent(element, eventName, callback: MyCallback) {
    element.addEventListener(eventName, callback, false);
}
Daniel
źródło