Storia della domanda:
Con lo sviluppo di TypeScript è emersa la necessità di definire in modo affidabile un tipo più specifico di variabile nei rami logici. I controlli di tipo classici (tramite typeof o instanceof) non sono sempre sufficienti, soprattutto se un oggetto ha una struttura complessa o una gerarchia. Per migliorare la sicurezza dei dati e la praticità, TypeScript ha implementato il meccanismo dei tipo di predicato per creare type guard personalizzati.
Problema:
Controlli di tipo normali all'interno delle funzioni non forniscono al compilatore informazioni sul tipo di variabile nel codice successivo, se si utilizza solo il risultato true/false. Il compilatore non "comprende" cosa sia stato controllato. Questo porta a errori impliciti a runtime quando, per errore, accediamo a proprietà inesistenti.
Soluzione:
I tipi di predicato (type predicates) utilizzando un tipo di sostituzione come 'param is Type' informano il compilatore che con questo parametro, dopo la chiamata al controllo, si può lavorare come con un tipo specifico. Queste funzioni aumentano la sicurezza del tipo e espandono il sistema di restringimento dei tipi per affrontare qualsiasi compito complesso.
Esempio di codice:
interface Bird { fly(): void; feathers: boolean; } interface Fish { swim(): void; fins: number; } function isBird(animal: Bird | Fish): animal is Bird { return (animal as Bird).fly !== undefined; } const pet: Bird | Fish = ...; if (isBird(pet)) { pet.fly(); // OK: pet ora è Bird } else { pet.swim(); // OK: pet ora è Fish }
Caratteristiche chiave:
Può una funzione type guard funzionare se non viene specificato esplicitamente il tipo restituito 'param is Type' nella firma?
No, se non viene specificato esplicitamente 'param is Type' nella firma, TypeScript non può restringere il tipo nei rami del codice, nonostante il valore restituito true/false. Il compilatore non capirà che il parametro può essere utilizzato come un tipo specifico.
Esempio di codice:
function isFish(animal: Fish | Bird): boolean { return (animal as Fish).swim !== undefined; } // Funziona? if (isFish(pet)) { pet.swim(); // Errore: Property 'swim' does not exist }
È possibile utilizzare i type predicates per controllare valori primitivi, come stringhe o numeri?
Sì, è possibile, ma più frequentemente si utilizza typeof e tali guard diventano superflui. Tuttavia, nulla impedisce di implementare un guard personalizzato:
function isString(x: unknown): x is string { return typeof x === "string"; }
Una funzione type guard fornisce una rigorosa protezione dagli errori di tipo in fase di compilazione?
Non completamente. TypeScript dipende dall'implementazione della funzione stessa e non può verificare la correttezza della logica al suo interno. Se implementi in modo errato il controllo, il compilatore non capirà l'errore e si presenteranno problemi a runtime.
function isFish(animal: Fish | Bird): animal is Fish { // Errato: restituisce sempre true return true; }
** Caso negativo Uno sviluppatore ha implementato una funzione predicato, ma ha commesso un errore nel controllo della struttura, facendo sì che la funzione restituisse sempre true. Il codice ha superato la compilazione, ma a runtime si è verificato un chiamata a un metodo inesistente.
Vantaggi:
Svantaggi:
** Caso positivo Le funzioni di predicato di tipo sono implementate correttamente, testate unitariamente su valori limite e dati errati.
Vantaggi:
Svantaggi: