Un tipo funzione descrive i parametri e il valore di ritorno di una funzione, scritto con una freccia: (a: number, b: number) => number. Dagli un nome con type e potrai usarlo per variabili, parametri e proprietà di oggetti.
La funzione assegnata ad add non ha bisogno di annotazioni: i tipi dei suoi parametri arrivano da Operation. Si chiama contextual typing ed è il motivo principale per dare un nome ai tipi funzione.
Sintassi dei tipi funzione
La forma è (parameters) => ReturnType. Ogni parametro ha bisogno di un nome e di un tipo. I nomi servono per la leggibilità e per i suggerimenti dell'editor; qualsiasi funzione con tipi di parametro compatibili corrisponde, comunque li chiami.
type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void; // rest parameter
type Factory = () => { id: number }; // no parameters
Il nome non è facoltativo. (string) => void dichiara un parametro chiamato string senza tipo, e la modalità strict lo rifiuta con TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?. Scrivi (value: string) => void.
Tipizzare le arrow function
Una arrow function si può tipizzare in due modi. Annota la funzione stessa, oppure annota la variabile con un tipo funzione e lascia che i parametri vengano dedotti.
La forma 1 è la scelta abituale per una funzione a sé. La forma 3 conviene quando più funzioni condividono la stessa firma (handler, comparatori, convertitori), perché la firma si scrive una volta sola e ogni sua modifica viene controllata ovunque.
Tipi delle callback
Un parametro il cui tipo è un tipo funzione è una callback. La funzione di chi chiama viene controllata rispetto a quel tipo e i suoi parametri vengono dedotti da lì.
Due regole rendono comode le callback:
- Meno parametri va bene. Una funzione che prende un parametro si può passare dove ne vengono offerti due. JavaScript ignora gli argomenti in più, quindi TypeScript lo permette. Più parametri di quanti ne offra il tipo è un errore (
Target signature provides too few arguments.). - Un ritorno
voidaccetta qualsiasi cosa. Una callback tipizzata(...) => voidpuò restituire un valore; chi chiama promette di non usarlo. Ecco perchélist.forEach((x) => other.push(x))compila anche sepushrestituisce un numero.
I tipi dei parametri devono comunque essere compatibili. Passare (x: string) => ... dove è atteso (a: number, b: number) => number fallisce con Types of parameters 'x' and 'a' are incompatible.
Call signature
(n: number) => string è la forma abbreviata di un tipo oggetto con una call signature: { (n: number): string }. Ti serve la forma lunga quando la funzione porta con sé anche delle proprietà.
TypeScript ti permette di aggiungere proprietà a una funzione dichiarata con const nello stesso scope, e le tiene traccia nel tipo della funzione. Anche un'interfaccia può contenere una call signature: interface Counter { (): number; count: number }.
Una construct signature descrive qualcosa che si chiama con new. Aggiunge new davanti: new (name: string) => User, oppure { new (name: string): User } in forma di oggetto. È il modo per tipizzare un parametro che riceve una classe.
Sintassi di metodo e sintassi di proprietà
In un tipo oggetto, un membro funzione si può scrivere come metodo, handle(value: string): void, oppure come proprietà che contiene una funzione, handle: (value: string) => void. Sembrano intercambiabili, ma con strict vengono controllati in modo diverso:
Il compilatore segnala:
index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
L'errore ha ragione: onlyStrings non sa gestire un numero. La forma a metodo lascia passare lo stesso errore (i parametri dei metodi vengono controllati in modo "bivariante" per motivi storici), e a.handle(42) andrebbe in crash a runtime con TypeError: value.toUpperCase is not a function. Cancella la riga di b per vedere il crash. Quando scrivi i tuoi tipi oggetto, la forma a proprietà intercetta più errori.
Evita il tipo Function
Function è il tipo integrato che ogni valore funzione soddisfa. Quasi mai è quello che ti serve: TypeScript non conosce né i parametri né il tipo di ritorno, quindi ogni chiamata viene accettata e restituisce any.
Sostituisci Function con la firma vera. Quando accetti davvero qualsiasi funzione (un debounce generico, un wrapper di logging), (...args: never[]) => unknown accetta ogni funzione e mantiene comunque il risultato unknown invece di any. Per le funzioni i cui tipi dipendono dall'input, vedi generics: un tipo funzione generico ha la forma <T>(value: T) => T.
Domande frequenti
Come si definisce un tipo funzione in TypeScript?
Usa la sintassi a freccia: (a: number, b: number) => number. Dagli un nome con un alias di tipo, type Compare = (a: number, b: number) => number;, e usa quel nome per variabili, parametri e proprietà. I nomi dei parametri fanno parte della sintassi ma servono solo come documentazione: una funzione con nomi di parametro diversi corrisponde comunque.
Come si tipizza un parametro callback in TypeScript?
Scrivi il tipo funzione come tipo del parametro: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. Chi chiama può passare una funzione con meno parametri, e i parametri della callback vengono dedotti da quel tipo, quindi onEach(list, (item) => ...) non ha bisogno di annotazioni.
Perché non usare il tipo Function in TypeScript?
Function accetta qualsiasi funzione e la sua chiamata non viene controllata: sono ammessi argomenti qualsiasi e il risultato è any. Scrivi invece la firma vera, come () => void o (value: string) => number, oppure (...args: never[]) => unknown quando accetti davvero qualsiasi funzione.
Che differenza c'è tra un tipo funzione e una call signature?
Descrivono la stessa cosa. (n: number) => string è la forma abbreviata del tipo oggetto { (n: number): string }. La forma con call signature serve quando la funzione ha anche delle proprietà, per esempio { (n: number): string; label: string }.
Come si tipizza una arrow function in TypeScript?
Puoi annotare i parametri e il tipo di ritorno direttamente, const half = (n: number): number => n / 2;, oppure dare alla variabile un tipo funzione e lasciare che i parametri vengano dedotti: const half: (n: number) => number = (n) => n / 2;.