ProgrammazioneSviluppatore Frontend

Come funziona TypeScript con i type guards definiti dall'utente (funzioni di protezione dei tipi personalizzate), a cosa servono e come creare funzioni di tipo predicato corrette? Quali trappole comuni si incontrano nella loro implementazione?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

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:

  • La verifica del tipo è implementata attraverso funzioni di tipo predicato speciali della forma param is Type.
  • È possibile creare i propri guards e utilizzarli per qualsiasi struttura, non solo per oggetti di classe.
  • Type guards non corretti possono portare a errori di runtime e a una riduzione errata del tipo.

Domande trabocchetto.

È 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.

Errori tipici e anti-pattern

  • Il type guard non restituisce un tipo di predicato, ma solo un booleano — non ci sono effetti per il compilatore.
  • Si controllano solo le chiavi superficiali, senza garantire l'integrità strutturale.
  • Il type guard restituisce sempre true, disattivando di fatto la protezione del tipo.
  • Il parametro in ingresso è troppo generico, oppure viene eseguita una coercizione di tipo implicita senza validazione.

Esempio dalla vita reale

Caso negativo

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:

  • Veloce, l'effetto è evidente in runtime

Svantaggi:

  • Dopo la filtrazione, il tipo dei dati non viene specificato per TypeScript, sono possibili errori nell'accesso alle proprietà

Caso positivo

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:

  • Il tipo dei risultati è esattamente noto
  • Riduce la probabilità di errori durante il lavoro successivo con il risultato

Svantaggi:

  • Richiede un po' più di attenzione alla firma e alla copertura delle verifiche