Le funzioni fortemente tipizzate come parametri sono possibili in TypeScript?


560

In TypeScript, posso dichiarare un parametro di una funzione come tipo Funzione. Esiste un modo "sicuro di scrivere" per fare ciò che mi manca? Ad esempio, considera questo:

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

Il callback di salvataggio non è di tipo sicuro, le sto assegnando una funzione di callback in cui il parametro della funzione è una stringa ma le sto passando un numero e lo compilo senza errori. Posso salvare il parametro risultato in una funzione di tipo sicuro?

TL; versione DR: esiste un equivalente di un delegato .NET in TypeScript?

Risposte:


805

Sicuro. Il tipo di una funzione è costituito dai tipi del suo argomento e dal suo tipo restituito. Qui specifichiamo che il callbacktipo di parametro deve essere "funzione che accetta un numero e restituisce il tipo 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

Se vuoi, puoi definire un tipo alias per incapsulare questo:

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

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

6
(n: number) => anysignifica qualche firma della funzione?
nikk wong,

16
@nikkwong significa che la funzione accetta un parametro (a number) ma il tipo restituito non è affatto limitato (potrebbe essere qualsiasi valore, o addirittura void)
Daniel Earwicker

16
Qual è il punto nin questa sintassi? I soli tipi di input e output non sarebbero sufficienti?
Yuhuan Jiang

4
Un effetto collaterale tra l'utilizzo di funzioni inline e funzioni con nome (risposta sotto vs questa risposta) è che la variabile "this" non è definita con la funzione con nome mentre è definita all'interno della funzione inline. Nessuna sorpresa per i programmatori JavaScript ma sicuramente non ovvio per altri sfondi di codifica.
Stevko,

3
@YuhuanJiang Questo post potrebbe interessarti
Ophidian

93

Ecco gli equivalenti TypeScript di alcuni delegati .NET comuni:

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

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

2
Probabilmente utile da guardare, ma sarebbe un anti-pattern usare effettivamente tali tipi. Comunque quelli assomigliano più ai tipi Java SAM che ai delegati C #. Certo che non lo sono e sono equivalenti alla forma alias del tipo che è solo più elegante per le funzioni
Aluan Haddad

5
@AluanHaddad potresti approfondire il motivo per cui pensi che questo sia un anti-schema?
Max R McCarty,

8
Il motivo è che TypeScript ha una sintassi letterale di tipo funzione concisa che ovvia alla necessità di tali interfacce. In C # i delegati sono nominali, ma i delegati Actione Funcentrambi ovviano alla maggior parte della necessità di specifici tipi di delegato e, cosa interessante, danno a C # una parvenza di tipizzazione strutturale. L'aspetto negativo di questi delegati è che i loro nomi non trasmettono alcun significato, ma gli altri vantaggi generalmente superano questo. In TypeScript non abbiamo semplicemente bisogno di questi tipi. Quindi l'anti-modello sarebbe function map<T, U>(xs: T[], f: Func<T, U>). Preferirefunction map<T, U>(xs: T[], f: (x: T) => U)
Aluan Haddad

6
È una questione di gusti, poiché si tratta di forme equivalenti in una lingua che non ha tipi di runtime. Al giorno d'oggi puoi anche usare alias di tipo anziché interfacce.
Drew Noakes,

18

Mi rendo conto che questo post è vecchio, ma esiste un approccio più compatto leggermente diverso da quello che è stato chiesto, ma potrebbe essere un'alternativa molto utile. È possibile dichiarare essenzialmente la funzione in-line quando si chiama il metodo ( Foo's save()in questo caso). Sarebbe simile a questo:

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

L' multipleCallback()approccio è molto utile per cose come le chiamate di rete che potrebbero avere esito positivo o negativo. Sempre assumendo un esempio di chiamata di rete, quandomultipleCallbacks() viene chiamato, il comportamento sia per un successo che per un fallimento può essere definito in un punto, il che si presta a una maggiore chiarezza per i futuri lettori di codici.

In generale, nella mia esperienza, questo approccio si presta ad essere più conciso, meno disordinato e una maggiore chiarezza generale.

Buona fortuna a tutti!


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

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

Questo sicuramente si allinea al paradigma di programmazione funzionale.


6
Dovresti chiamarlo inputTypepiuttosto che returnTypeno? Dov'è inputTypeil tipo di datacui si passa un parametro alla callbackfunzione.
ChrisW,

Sì @ChrisW hai ragione, inputType ha più senso. Grazie!
Krishna Ganeriwal il

2

In TS possiamo digitare le funzioni nei seguenti modi:

Tipi di funzioni / firme

Questo è usato per implementazioni reali di funzioni / metodi ha la seguente sintassi:

(arg1: Arg1type, arg2: Arg2type) : ReturnType

Esempio:

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

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

}

Tipo di funzione letterali

I letterali del tipo di funzione sono un altro modo per dichiarare il tipo di funzione. Di solito vengono applicati nella firma della funzione di una funzione di ordine superiore. Una funzione di ordine superiore è una funzione che accetta funzioni come parametri o che restituisce una funzione. Ha la seguente sintassi:

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

Esempio:

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

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

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

}

1

Se si definisce prima il tipo di funzione, sarebbe simile

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

Senza il tipo di funzione usando la sintassi della proprietà plain sarebbe:

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

Se si desidera utilizzare una funzione di interfaccia come i delegati generici c # sarebbe:

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

Oltre a quanto affermato da altri, un problema comune è dichiarare i tipi della stessa funzione sovraccaricata. Il caso tipico è il metodo EventEmitter on () che accetta più tipi di listener. Simile potrebbe accadere quando si lavora con azioni redux - e lì si utilizza il tipo di azione come letterale per contrassegnare il sovraccarico, in caso di EventEmitters, si utilizza il tipo letterale di nome evento:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.