Storia della questione: TypeScript estende le normali verifiche di tipo, consentendo agli sviluppatori di creare le proprie funzioni personalizzate – type guards – che verificano se un oggetto corrisponde a un tipo specifico. Questo è necessario per lavorare con tipi union, strutture dinamiche e API, dove il tipo di valore può variare.
Il problema è spesso che le normali verifiche di tipo tramite typeof e instanceof sono limitate ai primitivi e alle classi, mentre strutture o tipi complessi non possono essere identificati in questo modo. È necessario essere in grado di indicare esplicitamente al compilatore in quali casi il valore è ridotto in modo sicuro al tipo desiderato.
La soluzione è scrivere funzioni di protezione del tipo come function isCat(obj: Animal): obj is Cat {...}, dove il punto chiave è il tipo predicato nel tipo di ritorno della funzione.
Esempio di codice:
interface Dog { bark: () => void; } interface Cat { meow: () => void; } type Pet = Dog | Cat; function isDog(pet: Pet): pet is Dog { return (pet as Dog).bark !== undefined; } function makeSound(pet: Pet) { if (isDog(pet)) { pet.bark(); } else { pet.meow(); } }
Caratteristiche chiave:
È sufficiente che una funzione type guard restituisca true/false affinché il compilatore riduca il tipo?
No. È importante specificare esplicitamente il tipo di ritorno sotto forma di tipo predicato (ad esempio, pet is Dog), altrimenti TypeScript non ridurrà automaticamente il tipo del valore, anche se la funzione restituisce solo true o false.
È possibile utilizzare un type guard all'interno di un callback (ad esempio, in filter), e la riduzione funzionerà correttamente?
Sì, se il type guard è correttamente annotato, il compilatore ridurrà il tipo degli elementi dell'array dopo il filtro e all'interno delle funzioni forEach/callback. Ma se l'annotazione manca o è scritta in modo errato, il risultato avrà un tipo union invece di un tipo specificato.
const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // TypeScript sa che dogs: Dog[]
Qual è la differenza tra i type guards personalizzati e le normali verifiche di tipo tramite typeof, instanceof?
Le funzioni type guard consentono di implementare controlli per qualsiasi struttura, descrivere verifiche di qualsiasi complessità, operare su interfacce senza classe e non solo sui tipi e classi di base.
La funzione filtra gli utenti, realizzando un type guard senza tipo predicato:
function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]
Vantaggi:
Svantaggi:
La funzione filtra con un tipo predicato corretto:
interface Admin { name: string; isAdmin: true; } function isAdmin(user: any): user is Admin { return user && user.isAdmin === true; } const admins = users.filter(isAdmin); // admins: Admin[]
Vantaggi:
Svantaggi: