Menentukan jenis panggilan balik TypeScript

173

Saya mendapat kelas berikut di TypeScript:

class CallbackTest
{
    public myCallback;

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

Saya menggunakan kelas seperti ini:

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

Kode berfungsi, sehingga menampilkan kotak pesan seperti yang diharapkan.

Pertanyaan saya adalah: Apakah ada tipe apa pun yang dapat saya sediakan untuk bidang kelas saya myCallback? Saat ini, bidang publik myCallbackadalah tipe anyseperti yang ditunjukkan di atas. Bagaimana saya bisa menentukan metode tanda tangan panggilan balik? Atau bisakah saya mengatur tipe ke beberapa tipe callback? Atau bisakah aku melakukan ini? Apakah saya harus menggunakan any(implisit / eksplisit)?

Saya mencoba sesuatu seperti ini, tetapi tidak berhasil (kesalahan waktu kompilasi):

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

Saya tidak dapat menemukan penjelasan apa pun untuk online ini, jadi saya harap Anda dapat membantu saya.

nikeee
sumber

Jawaban:

212

Saya baru saja menemukan sesuatu dalam spesifikasi bahasa TypeScript, cukup mudah. Saya cukup dekat.

sintaksnya adalah sebagai berikut:

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

Dalam contoh saya, itu akan menjadi

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

    public doWork(): void
    {
        //doing some work...
        this.myCallback(); //calling callback
    }
}
nikeee
sumber
8
Saya tidak mengerti mengapa nama parameter diperlukan untuk menentukan tanda tangan panggilan balik ...
2grit
4
Saya kira itu mungkin warisan budaya dari tim C # , saya pikir saya menyukainya ...
2grit
@nikeee dapatkah Anda memberikan tautan ke halaman dokumentasi itu?
jcairney
Ini bisa menjadi tautan yang bagus fettblog.eu/typescript-substitutability
nilakantha singh deo
148

Untuk melangkah lebih jauh, Anda bisa mendeklarasikan pointer tipe ke tanda tangan fungsi seperti:

interface myCallbackType { (myArgument: string): void }

dan gunakan seperti ini:

public myCallback : myCallbackType;
Leng
sumber
9
Ini adalah (IMO) solusi yang jauh lebih baik daripada jawaban yang diterima, karena ini memungkinkan Anda menentukan jenis dan kemudian, katakanlah, melewati parameter jenis itu (panggilan balik) yang kemudian dapat Anda gunakan dengan cara apa pun yang Anda inginkan, termasuk memanggilnya. Jawaban yang diterima menggunakan variabel anggota dan Anda harus mengatur variabel anggota ke fungsi Anda, lalu memanggil metode - jelek dan rentan terhadap kesalahan, karena pengaturan variabel pertama adalah bagian dari kontrak memanggil metode.
David
Ini juga memungkinkan Anda dengan mudah menetapkan callback sebagai nullable, misalnyalet callback: myCallbackType|null = null;
Doches
1
Perhatikan bahwa TSLint akan mengeluh "TSLint: Antarmuka hanya memiliki tanda tangan panggilan - gunakan type MyHandler = (myArgument: string) => voidsaja. (Tipe panggilan )" ; lihat jawaban TSV
Arjan
Draf awal jawaban ini sebenarnya memecahkan masalah yang mengarahkan saya ke pertanyaan ini. Saya telah mencoba untuk mendefinisikan tanda tangan fungsi yang cukup permisif dalam antarmuka yang dapat menerima sejumlah parameter tanpa menghasilkan kesalahan kompiler. Jawaban dalam kasus saya adalah menggunakan ...args: any[]. Contoh: ekspor antarmuka MyInterface {/ ** Fungsi panggilan balik. / callback: (... args: any []) => any, / * Parameter untuk fungsi callback. * / callbackParams: any []}
Ken Lyon
62

Anda dapat mendeklarasikan tipe baru:

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

var handler: MyHandler;

Memperbarui.

Kata declarekunci tidak perlu. Ini harus digunakan dalam file .d.ts atau dalam kasus serupa.

TSV
sumber
Di mana saya menemukan dokumentasi untuk ini?
E. Sundin
@ E.Sundin - Bagian "Ketik Alias" dari typescriptlang.org/docs/handbook/advanced-types.html
TSV
1
Meskipun benar dan menyenangkan untuk diketahui, halaman yang sama (saat ini) juga menyatakan "Karena properti ideal perangkat lunak terbuka untuk ekstensi, Anda harus selalu menggunakan antarmuka melalui jenis alias jika mungkin."
Arjan
@ Arjan - Saya sangat setuju dengan ini untuk objek. Bisakah Anda menentukan - bagaimana Anda ingin memperluas fungsi?
TSV
Perhatikan bahwa deklarasi tipe adalah opsional: var handler: (myArgument: string) => voidsecara sintaksis valid (jika agak berantakan).
Hutch
36

Ini adalah contohnya - tidak menerima parameter dan tidak mengembalikan apa pun.

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

Jika Anda ingin menerima parameter, Anda dapat menambahkannya juga:

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

Dan jika Anda ingin mengembalikan nilai, Anda dapat menambahkannya juga:

public myCallback: {(msg: string): number;};
Fenton
sumber
Secara fungsional mereka identik - mereka mendefinisikan hal yang sama dan memberi Anda mengetik memeriksa tanda tangan fungsi. Anda dapat menggunakan mana yang Anda inginkan. Spesifikasi mengatakan mereka exactly equivalent.
Fenton
6
@nikeee: Pertanyaannya agak apa yang berbeda dengan jawaban Anda? Steve memposting jawabannya di depan Anda.
jgauffin
@ jgauffin Memang, hasilnya sama. IMO solusi yang saya posting lebih alami ketika berbicara tentang panggilan balik, karena versi Steve memungkinkan definisi seluruh antarmuka. Itu tergantung pada preferensi Anda.
nikeee
@Fenton, bisakah Anda memberikan tautan ke dokumentasi itu?
jcairney
18

Jika Anda menginginkan fungsi generik, Anda dapat menggunakan yang berikut ini. Meskipun sepertinya tidak didokumentasikan di mana pun.

class CallbackTest {
  myCallback: Function;
}   
Ash Blue
sumber
4

Anda dapat menggunakan yang berikut ini:

  1. Ketik Alias ​​(menggunakan typekata kunci, aliasing fungsi literal)
  2. Antarmuka
  3. Fungsi Literal

Berikut adalah contoh cara menggunakannya:

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
sumber
2

Saya menemukan kesalahan yang sama ketika mencoba menambahkan panggilan balik ke pendengar acara. Anehnya, pengaturan jenis panggilan balik ke EventListener menyelesaikannya. Itu terlihat lebih elegan daripada mendefinisikan tanda tangan seluruh fungsi sebagai tipe, tapi saya tidak yakin apakah ini cara yang benar untuk melakukan ini.

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
sumber
suka itu. Tapi di mana kelas lain beraksi?
Yaro
2

Saya sedikit terlambat, tetapi, karena beberapa waktu lalu dalam TypeScript Anda dapat menentukan jenis panggilan balik dengan

type MyCallback = (KeyboardEvent) => void;

Contoh penggunaan:

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

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