Apakah fungsi yang diketik dengan kuat sebagai parameter mungkin dalam TypeScript?

559

Di TypeScript, saya bisa mendeklarasikan parameter fungsi sebagai tipe Function. Apakah ada cara "aman untuk" melakukan hal ini yang saya lewatkan? Sebagai contoh, pertimbangkan ini:

class Foo {
    save(callback: Function) : void {
        //Do the save
        var result : number = 42; //We get a number from the save operation
        //Can I at compile-time ensure the callback accepts a single parameter of type number somehow?
        callback(result);
    }
}

var foo = new Foo();
var callback = (result: string) : void => {
    alert(result);
}
foo.save(callback);

Simpan callback bukan tipe aman, saya memberinya fungsi callback di mana parameter fungsi adalah string tetapi saya meneruskannya nomor, dan mengkompilasi tanpa kesalahan. Bisakah saya membuat parameter hasil dalam menyimpan fungsi tipe-aman?

TL; Versi DR: apakah ada yang setara dengan delegasi .NET di TypeScript?

vcsjones
sumber

Jawaban:

805

Tentu. Tipe fungsi terdiri dari tipe argumennya dan tipe pengembaliannya. Di sini kami menentukan bahwa tipe callbackparameter harus "fungsi yang menerima nomor dan mengembalikan tipe any":

class Foo {
    save(callback: (n: number) => any) : void {
        callback(42);
    }
}
var foo = new Foo();

var strCallback = (result: string) : void => {
    alert(result);
}
var numCallback = (result: number) : void => {
    alert(result.toString());
}

foo.save(strCallback); // not OK
foo.save(numCallback); // OK

Jika mau, Anda dapat menentukan jenis alias untuk merangkum ini:

type NumberCallback = (n: number) => any;

class Foo {
    // Equivalent
    save(callback: NumberCallback) : void {
        callback(42);
    }
}
Ryan Cavanaugh
sumber
6
(n: number) => anyberarti ada tanda tangan fungsi?
nikk wong
16
@nikkwong itu artinya fungsi tersebut mengambil satu parameter (a number) tetapi tipe kembali tidak dibatasi sama sekali (bisa berupa nilai apa pun, atau bahkan void)
Daniel Earwicker
16
Apa gunanya nsintaks ini? Bukankah tipe input dan output saja sudah cukup?
Yuhuan Jiang
4
Salah satu efek samping antara menggunakan fungsi inline vs fungsi bernama (jawaban di bawah vs jawaban ini) adalah variabel "ini" tidak terdefinisi dengan fungsi yang dinamai sedangkan itu didefinisikan dalam fungsi inline. Tidak mengherankan bagi para pembuat kode JavaScript tetapi jelas tidak jelas bagi latar belakang pengkodean lainnya.
Stevko
3
@YuhuanJiang Posting ini mungkin menarik bagi Anda
Ophidian
93

Berikut ini adalah setara dengan TypeScript dari beberapa delegasi .NET umum:

interface Action<T>
{
    (item: T): void;
}

interface Func<T,TResult>
{
    (item: T): TResult;
}
Drew Noakes
sumber
2
Mungkin berguna untuk dilihat tetapi akan menjadi anti-pola untuk benar-benar menggunakan tipe seperti itu. Pokoknya itu lebih mirip tipe Java SAM daripada delegasi C #. Tentu saja mereka tidak dan mereka setara dengan jenis alias bentuk yang hanya lebih elegan untuk fungsi
Aluan Haddad
5
@AlHanHaddad dapatkah Anda menjelaskan mengapa Anda akan berpikir ini anti-pola?
Max R McCarty
8
Alasannya adalah TypeScript memiliki fungsi ringkas jenis sintaks literal yang meniadakan kebutuhan untuk antarmuka tersebut. Dalam C # delegasi adalah nominal, tetapi delegasi Actiondan Funckeduanya meniadakan sebagian besar kebutuhan untuk tipe delegasi tertentu dan, yang menarik, memberikan C # kemiripan pengetikan struktural. Kelemahan dari delegasi ini adalah bahwa nama mereka tidak memiliki arti tetapi keuntungan lain biasanya lebih besar dari ini. Dalam TypeScript kita tidak perlu tipe ini. Jadi anti-polanya adalah function map<T, U>(xs: T[], f: Func<T, U>). Lebih sukafunction map<T, U>(xs: T[], f: (x: T) => U)
Aluan Haddad
6
Ini masalah selera, karena ini adalah bentuk setara dalam bahasa yang tidak memiliki tipe run-time. Saat ini Anda juga dapat menggunakan alias alias alih-alih antarmuka.
Drew Noakes
18

Saya menyadari posting ini sudah tua, tetapi ada pendekatan yang lebih kompak yang sedikit berbeda dari yang diminta, tetapi mungkin merupakan alternatif yang sangat membantu. Anda pada dasarnya dapat mendeklarasikan fungsi in-line saat memanggil metode ( Foo's save()dalam kasus ini). Akan terlihat seperti ini:

class Foo {
    save(callback: (n: number) => any) : void {
        callback(42)
    }

    multipleCallbacks(firstCallback: (s: string) => void, secondCallback: (b: boolean) => boolean): void {
        firstCallback("hello world")

        let result: boolean = secondCallback(true)
        console.log("Resulting boolean: " + result)
    }
}

var foo = new Foo()

// Single callback example.
// Just like with @RyanCavanaugh's approach, ensure the parameter(s) and return
// types match the declared types above in the `save()` method definition.
foo.save((newNumber: number) => {
    console.log("Some number: " + newNumber)

    // This is optional, since "any" is the declared return type.
    return newNumber
})

// Multiple callbacks example.
// Each call is on a separate line for clarity.
// Note that `firstCallback()` has a void return type, while the second is boolean.
foo.multipleCallbacks(
    (s: string) => {
         console.log("Some string: " + s)
    },
    (b: boolean) => {
        console.log("Some boolean: " + b)
        let result = b && false

        return result
    }
)

The multipleCallback()Pendekatan ini sangat berguna untuk hal-hal seperti panggilan jaringan yang mungkin berhasil atau gagal. Sekali lagi dengan asumsi contoh panggilan jaringan, kapanmultipleCallbacks() dipanggil, perilaku untuk sukses dan gagal dapat didefinisikan di satu tempat, yang cocok untuk kejelasan yang lebih besar bagi pembaca kode masa depan.

Secara umum, dalam pengalaman saya, pendekatan ini cocok untuk menjadi lebih ringkas, kurang berantakan, dan lebih jelas secara keseluruhan.

Semoga beruntung semuanya!

kbpontius
sumber
16
type FunctionName = (n: inputType) => any;

class ClassName {
    save(callback: FunctionName) : void {
        callback(data);
    }
}

Ini tentunya sejalan dengan paradigma pemrograman fungsional.

Krishna Ganeriwal
sumber
6
Anda harus menyebutnya inputTypebukan returnType, bukan? Di mana inputTypejenis datayang Anda berikan parameter ke callbackfungsi.
ChrisW
Ya @ Chris Anda benar, inputType lebih masuk akal. Terima kasih!
Krishna Ganeriwal
2

Di TS kita bisa mengetikkan fungsi-fungsi di dalam tata cara berikut:

Jenis fungsi / tanda tangan

Ini digunakan untuk implementasi nyata dari fungsi / metode yang memiliki sintaksis berikut:

(arg1: Arg1type, arg2: Arg2type) : ReturnType

Contoh:

function add(x: number, y: number): number {
    return x + y;
}

class Date {
  setTime(time: number): number {
   // ...
  }

}

Tipe Fungsi Literal

Literal tipe fungsi adalah cara lain untuk mendeklarasikan tipe fungsi. Mereka biasanya diterapkan dalam tanda tangan fungsi dari fungsi tingkat tinggi. Fungsi tingkat tinggi adalah fungsi yang menerima fungsi sebagai parameter atau yang mengembalikan fungsi. Ini memiliki sintaks berikut:

(arg1: Arg1type, arg2: Arg2type) => ReturnType

Contoh:

type FunctionType1 = (x: string, y: number) => number;

class Foo {
    save(callback: (str: string) => void) {
       // ...
    }

    doStuff(callback: FunctionType1) {
       // ...
    }

}
Willem van der Veen
sumber
1

Jika Anda menentukan jenis fungsi terlebih dahulu maka akan terlihat seperti

type Callback = (n: number) => void;

class Foo {
    save(callback: Callback) : void {        
        callback(42);
    }
}

var foo = new Foo();
var stringCallback = (result: string) : void => {
    console.log(result);
}

var numberCallback = (result: number) : void => {
    console.log(result);
}

foo.save(stringCallback); //--will be showing error
foo.save(numberCallback);

Tanpa tipe fungsi dengan menggunakan sintaks properti biasa akan menjadi:

class Foo {
    save(callback: (n: number) => void) : void {        
        callback(42);
    }
}

var foo = new Foo();
var stringCallback = (result: string) : void => {
    console.log(result);
}

var numberCallback = (result: number) : void => {
    console.log(result);
}

foo.save(stringCallback); //--will be showing error
foo.save(numberCallback);

Jika Anda ingin menggunakan fungsi antarmuka seperti delegasi c # generiknya adalah:

interface CallBackFunc<T, U>
{
    (input:T): U;
};

class Foo {
    save(callback: CallBackFunc<number,void>) : void {        
        callback(42);
    }
}

var foo = new Foo();
var stringCallback = (result: string) : void => {
    console.log(result);
}

var numberCallback = (result: number) : void => {
    console.log(result);
}

let strCBObj:CallBackFunc<string,void> = stringCallback;
let numberCBObj:CallBackFunc<number,void> = numberCallback;

foo.save(strCBObj); //--will be showing error
foo.save(numberCBObj);
Humayoun_Kabir
sumber
0

Selain apa yang dikatakan orang lain, masalah umum adalah mendeklarasikan jenis fungsi yang sama yang kelebihan beban. Kasus umum adalah metode EventEmitter on () yang akan menerima beberapa jenis pendengar. Serupa bisa terjadi Ketika bekerja dengan aksi redux - dan di sana Anda menggunakan tipe tindakan sebagai literal untuk menandai kelebihan beban, Jika CaseEmitters, Anda menggunakan tipe literal nama acara:

interface MyEmitter extends EventEmitter {
  on(name:'click', l: ClickListener):void
  on(name:'move', l: MoveListener):void
  on(name:'die', l: DieListener):void
  //and a generic one
  on(name:string, l:(...a:any[])=>any):void
}

type ClickListener = (e:ClickEvent)=>void
type MoveListener = (e:MoveEvent)=>void
... etc

// will type check the correct listener when writing something like:
myEmitter.on('click', e=>...<--- autocompletion
kankerbero
sumber