Parametri di tipo in Typescript | Aulab

GUIDE PER ASPIRANTI PROGRAMMATORI

Parametri di tipo in Typescript

A volte può tornarci comodo parametrizzare i tipi allo stesso modo in cui facciamo con i dati. Prendiamo una funzione estremamente semplice, che accetta un dato e ritorna lo stesso dato immutato; come definiremmo la sua firma in TypeScript? const identity = x => x; // identity: ?? In questo esempio, TypeScript si limita a…

Lezione 24 / 30
Enza Neri
Immagine di copertina

Vuoi avviare una nuova carriera o fare un upgrade?

Trova il corso Digital & Tech più adatto a te nel nostro catalogo!

A volte può tornarci comodo parametrizzare i tipi allo stesso modo in cui facciamo con i dati. Prendiamo una funzione estremamente semplice, che accetta un dato e ritorna lo stesso dato immutato; come definiremmo la sua firma in TypeScript?

const identity = x => x; // identity: ??

In questo esempio, TypeScript si limita a dirci che x è implicitamente di tipo any e, infatti, il tipo inferito di identity è (x: any) => any, che non è molto soddisfacente: sappiamo benissimo che il tipo di ritorno è esattamente identico al tipo dell’argomento passato, perché è evidente dall’implementazione. 

Possiamo, dunque, parametrizzare il tipo di x insieme a x:

const identity = <T>(x: T) => x;

In questo modo il tipo di identity è <T>(x: T) => T, cioè una funzione che prende un parametro di un certo tipo e restituisce lo stesso tipo del parametro passato.

Le definizioni che presentano parametri di tipo sono chiamate tipi generici (in inglese: generics), perché ci permettono di fissare dei vincoli di tipo senza concretizzare un tipo particolare. Oltre alle funzioni, i parametri di tipo possono essere usati nella definizione di nuovi tipi: 

interface Has<T> { value: T; } 
type Maybe<T> = T | null; 
type MaybeNumber = Maybe<number>;

Abbiamo già incontrato un tipo generico molto comune: Array<T>, anche noto come T[]. Grazie ai generics, possiamo definire tipi che sono di fatto funzioni su tipi, cioè funzioni che accettano tipi come argomenti e restituiscono nuovi tipi. È esattamente ciò che accade con Maybe<T> e MaybeNumber, tanto per fare un esempio. 

Proprio come i parametri nel linguaggio JavaScript vengono vincolati ad un certo tipo in TypeScript con le annotazioni di tipo, anche i parametri di tipo possono essere vincolati ad appartenere a determinate categorie di tipi; per farlo usiamo la parola chiave extends

type Primitive = boolean | number | string; 
interface Box<T extends Primitive> { 
 value: T; 
} 
const box = <T extends Primitive>(value: T): Box<T> => ({ value }); const unbox = <T extends Primitive>(box: Box<T>): T => box.value; 
const boxedNumber = box(42); // boxedNumber: Box<42> 
const boxedBoolean = box(unbox(boxedNumber) + 1 === 43); // boxedBoolean: Box<boolean>

In questo esempio è come se avessimo tipizzato un parametro di tipo: abbiamo vincolato il parametro T a Primitive, dopodiché lo abbiamo trattato come un tipo a sé, motivo per cui TypeScript ha capito perfettamente che l’operazione all’ultima riga è una somma valida tra numeri. 

Ovviamente, in questo caso il vincolo a Primitive non era strettamente necessario, ma il senso di un tipo come Box è quello di incapsulare in un oggetto un valore, cosa che sarebbe ridondante se “boxassimo” un oggetto (che è già una box di valori primitivi). 

Avremmo, dunque, potuto limitarci a indicare Primitive, senza parametrizzare il tipo? Vediamo cosa sarebbe cambiato in quel caso: 

type Primitive = boolean | number | string; 
interface Box { 
 value: Primitive; 
}
const box = (value: Primitive): Box => ({ value }); 
const unbox = (box: Box): Primitive => box.value; 
const boxedNumber = box(42); // boxedNumber: Box 
const boxedBoolean = box(unbox(boxedNumber) + 1 === 43); // Operator '+' cannot be applied to types 'string | number | boolean' and 'number'.

Ora TypeScript non può più assicurare che l’addizione all’ultima riga sia valida, perché l’informazione su quale sottotipo di Primitive sia stato inscatolato in boxedNumber è andata persa. Usando il tipo più ampio come un vincolo anziché un tipo abbiamo fatto fare a TypeScript il narrowing in compilazione e, dunque, ce lo possiamo risparmiare in esecuzione

Sei indeciso sul percorso? 💭

Parliamone! Scrivici su Whatsapp e risponderemo a tutte le tue domande per capire quale dei nostri corsi è il più adatto alle tue esigenze.

Oppure chiamaci al 800 128 626