TypeScript'te parametreler olarak güçlü yazılan işlevler mümkün müdür?


559

TypeScript'te, bir işlevin parametresini tür İşlev olarak bildirebilirim. Bunu yapmamın "tipte güvenli" bir yolu var mı? Örneğin, şunu düşünün:

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

Save callback güvenli değildir, işlevin parametresinin bir dize olduğu bir geri çağırma işlevi veriyorum, ancak bir sayı geçiriyorum ve hatasız derler. Sonuç parametresini tip-güvenli bir fonksiyon kaydetmede yapabilir miyim?

TL; DR sürümü: TypeScript'te bir .NET temsilcisinin eşdeğeri var mı?

Yanıtlar:


805

Elbette. Bir işlevin türü , bağımsız değişken türlerinden ve dönüş türünden oluşur. Burada callbackparametrenin türünün "bir sayıyı kabul eden ve türü döndüren işlev" olması gerektiğini belirtiyoruz 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

İsterseniz, bunu kapsüllemek için bir tür takma adı tanımlayabilirsiniz :

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

class Foo {
    // Equivalent
    save(callback: NumberCallback) : void {
        callback(42);
    }
}

6
(n: number) => anyherhangi bir işlev imzası anlamına gelir?
nikk wong

16
@nikkwong, işlevin bir parametre (a number) aldığı anlamına gelir, ancak dönüş türü hiç kısıtlanmamıştır (herhangi bir değer olabilir veya hatta olabilir void)
Daniel Earwicker

16
nBu sözdiziminin anlamı nedir ? Giriş ve çıkış türleri tek başına yeterli olmaz mı?
Yuhuan Jiang

4
Satır içi işlevler ile adlandırılmış işlevler (aşağıda verilen yanıtla bu cevaba karşılık) kullanma arasındaki bir yan etki, "bu" değişkeninin adlandırılmış işlevle tanımlanmamış olması ve satır içi işlevde tanımlanmasıdır. JavaScript kodlayıcıları için sürpriz değil, ancak diğer kodlama arka planları için kesinlikle açık değil.
Stevko

3
@YuhuanJiang Bu yazı ilginizi çekebilir
Ophidian

93

Bazı yaygın .NET delegelerinin TypeScript eşdeğerleri şunlardır:

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

interface Func<T,TResult>
{
    (item: T): TResult;
}

2
Muhtemelen bakmak yararlıdır, ancak bu tür türleri kullanmak bir anti-desen olacaktır. Her neyse, bunlar C # delegelerinden daha çok Java SAM türlerine benziyor. Tabii ki değiller ve fonksiyonlar için daha zarif olan tür takma ad formuna eşdeğerler
Aluan Haddad

5
@AluanHaddad bunun neden bir anti-desen düşündüğünüzü açıklayabilir misiniz?
Max R McCarty

8
Bunun nedeni, TypeScript'in bu tür arabirimlere olan ihtiyacı ortadan kaldıran özlü bir işlev türü değişmez sözdizimine sahip olmasıdır. C # delegeleri nominaldir, ancak Actionve Funcdelegeler hem belirli delege türlerine duyulan ihtiyacın çoğunu ortadan kaldırır hem de ilginç bir şekilde C # 'a yapısal yazım benzerliğini verir. Bu delegelerin dezavantajı, isimlerinin bir anlam ifade etmemesidir, ancak diğer avantajlar genellikle bundan daha ağır basmaktadır. TypeScript'te bu türlere ihtiyacımız yok. Böylece anti-desen olurdu function map<T, U>(xs: T[], f: Func<T, U>). Tercihfunction map<T, U>(xs: T[], f: (x: T) => U)
Aluan Haddad

6
Bu bir zevk meselesidir, çünkü bunlar çalışma zamanı türleri olmayan bir dilde eşdeğer formlardır. Günümüzde arayüzler yerine tür takma adları da kullanabilirsiniz.
Drew Noakes

18

Bu yazının eski olduğunu biliyorum, ancak sorulandan biraz farklı olan daha kompakt bir yaklaşım var, ancak çok yararlı bir alternatif olabilir. Yöntemi (çağrılırken Sen esasen in-line fonksiyonu ilan edebilir Foo'ın save()bu durumda). Bunun gibi bir şey olurdu:

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
    }
)

Bu multipleCallback()yaklaşım, başarılı veya başarısız olabilen ağ aramaları gibi şeyler için çok yararlıdır. Yine bir ağ çağrısı örneğinin, multipleCallbacks()çağrıldığında, hem başarı hem de başarısızlık için davranış bir noktada tanımlanabilir ve bu da gelecekteki kod okuyucuları için daha fazla netlik sağlar.

Genel olarak, tecrübelerime göre, bu yaklaşım daha özlü, daha az dağınık ve genel olarak daha fazla netliğe sahiptir.

Hepinize iyi şanslar!


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

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

Bu kesinlikle işlevsel programlama paradigması ile uyumludur.


6
Bunun inputTypeyerine onu aramalısın returnType, değil mi? Nerede inputTypetürüdür datasen için parametre geçirmek hangi callbackişlevi.
ChrisW

Evet @ChrisW haklısın, inputType daha mantıklı. Teşekkürler!
Krishna Ganeriwal

2

TS'de aşağıdaki şekillerde işlevleri yazabiliriz:

İşlev türleri / imzalar

Bu, aşağıdaki sözdizimine sahip işlevlerin / yöntemlerin gerçek uygulamaları için kullanılır:

(arg1: Arg1type, arg2: Arg2type) : ReturnType

Misal:

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

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

}

İşlev Türü Değişmez değerleri

İşlev türü değişmezleri, işlev türünü bildirmenin başka bir yoludur. Genellikle üst düzey bir işlevin işlev imzasında uygulanır. Üst düzey işlev, işlevleri parametre olarak kabul eden veya işlevi döndüren bir işlevdir. Aşağıdaki sözdizimine sahiptir:

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

Misal:

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

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

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

}

1

Önce işlev türünü tanımlarsanız,

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

Plain özellik sözdizimini kullanarak işlev türü olmadan şöyle olur:

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

Eğer c # genel delegeler gibi bir arabirim işlevi kullanarak isterseniz:

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

0

Diğerlerinin söylediklerinin yanı sıra, ortak bir sorun, aşırı yüklenmiş aynı işlevin türlerini bildirmektir. Tipik durum, birden çok tür dinleyiciyi kabul edecek EventEmitter on () yöntemidir. Benzer olabilir Redux eylemleriyle çalışırken - ve aşırı yükü işaretlemek için eylem türünü hazır bilgi olarak kullanırsınız, EventEmitters durumunda, olay adı hazır bilgi türünü kullanırsınız:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.