
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…


Vuoi avviare una nuova carriera o fare un upgrade?
Trova il corso Digital & Tech più adatto a te nel nostro catalogo!
- Tipi primitivi in Typescript
- Tipi letterali in Typescript
- Null e undefined in Typescript
- Tipi enumerativi in Typescript
- Oggetti in Typescript
- Array e tuple in Typescript
- Funzioni in Typescript
- Guardie di tipo in Typescript
- Any vs unknown in Typescript
- Tipi algebrici in Typescript
- Parametri di tipo in Typescript
- Manipolazione di tipi in Typescript
- Tipi utility 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 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.
CONTENUTI GRATUITI IN EVIDENZA
Guide per aspiranti programmatori 👨🏻🚀
Vuoi muovere i primi passi nel Digital e Tech? Abbiamo preparato alcune guide per aiutarti a orientarti negli ambiti più richiesti oggi.