Lezioni del modulo (2/2)
Unioni e Restringimento
Nel mondo reale, le variabili e le risposte delle API non hanno sempre un singolo tipo fisso. TypeScript offre i Tipi Unione (Union Types) per gestire la variabilità, e il Restringimento dei Tipi (Type Narrowing) per operare in sicurezza su di essi a runtime.
Tipi Unione (Union Types)
Un tipo unione permette a una variabile di accettare valori di tipi differenti. Viene espresso tramite il simbolo della barra verticale (|):
let result: number | string;
result = 42; // Valido
result = 'Errore 404'; // ValidoTuttavia, quando lavoriamo con un tipo unione, non possiamo chiamare direttamente metodi che appartengono a uno solo dei tipi (ad esempio, non possiamo fare .toUpperCase() se la variabile può anche essere un number). Dobbiamo prima "restringere" il tipo.
Restringimento dei Tipi (Type Narrowing)
Il Type Narrowing è il processo con cui TypeScript analizza le strutture di controllo del flusso (come if o switch) per dedurre un tipo più specifico per una variabile a runtime.
Esistono diversi modi per effettuare il narrowing:
1. Operatore typeof
Ideale per distinguere i tipi primitivi:
function printLength(value: string | number) {
if (typeof value === 'string') {
// Qui TypeScript sa che 'value' è una stringa
console.log(value.length);
} else {
// Qui TypeScript sa che 'value' è un numero
console.log(value.toFixed(2));
}
}2. Operatore in
Utilizzato per verificare la presenza di una proprietà specifica in un oggetto:
interface Fish {
swim: () => void;
}
interface Bird {
fly: () => void;
}
function move(animal: Fish | Bird) {
if ('swim' in animal) {
animal.swim(); // Narrowing a Fish
} else {
animal.fly(); // Narrowing a Bird
}
}Unioni Discriminate (Discriminated Unions)
Il pattern delle Unioni Discriminate consiste nel creare oggetti che condividono una proprietà comune con un valore letterale univoco (chiamata discriminatore). TypeScript riconosce questo discriminatore ed effettua il narrowing automatico all'interno di blocchi condizionali.
interface SuccessResponse {
status: 'success'; // Discriminatore letterale
data: string;
}
interface ErrorResponse {
status: 'error'; // Discriminatore letterale
errorMessage: string;
}
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) {
if (response.status === 'success') {
console.log('Dati ricevuti:', response.data);
} else {
console.error('Si è verificato un errore:', response.errorMessage);
}
}Prova tu
Esercizio 1: Unione di Tipi
Dichiara una variabile chiamata id che può essere sia un numero che una stringa. Inizializzala prima con il numero 101, poi assegna il valore stringa 'USER-101'.
Mostra suggerimento
Usa l'operatore | per unire number e string nella dichiarazione della variabile let.
Soluzione disponibile dopo 3 tentativi
Esercizio 2: Type Narrowing Base
Crea una funzione chiamata formatInput che accetta un parametro input di tipo stringa o numero. Se input è una stringa, restituisci input convertito in maiuscolo. Se è un numero, restituisci input moltiplicato per 2. Specifica i tipi in modo esplicito.
Mostra suggerimento
Usa typeof input === 'string' all'interno di un blocco if per distinguere il comportamento.
Soluzione disponibile dopo 3 tentativi
Esercizio 3: Narrowing con 'in'
Date le due interfacce Car (con metodo drive) e Boat (con metodo sail), scrivi una funzione chiamata moveVehicle che accetta un parametro vehicle di tipo Car o Boat. Se vehicle ha la proprietà drive, esegui il metodo drive(). Altrimenti, esegui il metodo sail().
Mostra suggerimento
Usa l'operatore in nella forma 'drive' in vehicle per fare il narrowing dell'interfaccia.
Soluzione disponibile dopo 3 tentativi
Esercizio 4: Unione Discriminata Shape
Definisci un tipo Shape che è l'unione di due tipi: Circle e Square. Circle ha una proprietà kind impostata su 'circle' (valore letterale) e un radius (numero). Square ha una proprietà kind impostata su 'square' (valore letterale) e un side (numero). Scrivi poi una funzione getArea che accetta shape di tipo Shape e restituisce l'area come numero (per il cerchio Math.PI * radius * radius, per il quadrato side * side).
Mostra suggerimento
Usa shape.kind === 'circle' all'interno di getArea per discriminare il tipo e calcolare l'area corretta.
Soluzione disponibile dopo 3 tentativi