ProgrammazioneArchitetto TypeScript

Come viene implementato il pattern matching in TypeScript attraverso gli union discriminati? Come strutturare correttamente i tipi e quali insidie esistono?

Supera i colloqui con l'assistente IA Hintsage

Risposta

In TypeScript, il pattern matching è implementato tramite gli "union discriminati". A ciascun oggetto nell'unione viene assegnato un campo discriminatore obbligatorio (di solito una stringa, ad esempio type), in base al quale TypeScript differenzia le varianti.

Esempio:

type Success = { type: 'success'; data: string }; type Failure = { type: 'failure'; error: string }; type Result = Success | Failure; function handleResult(result: Result) { switch (result.type) { case 'success': // result: Success console.log(result.data); break; case 'failure': // result: Failure console.error(result.error); break; } }

In switch/case o if sul campo discriminatore, TypeScript "restringerà" il tipo esattamente alla variante desiderata.

Principali vantaggi:

  • Tipizzazione rigorosa — non è possibile accedere a campi inesistenti.
  • Verifica di exhaustiveness — se non vengono gestite tutte le varianti, a volte si attiva un errore (può essere forzato esplicitamente).

Domanda trabocchetto

Se aggiungi una nuova variante nell'unione discriminata, TypeScript richiederà forzatamente di aggiornare tutti i switch-case per gestire la nuova variante?

Risposta: No, solo se si aggiunge esplicitamente la gestione della variante "impossibile". Ad esempio, utilizzare la funzione never:

Esempio:

function assertNever(x: never): never { throw new Error('Variant imprevista: ' + x); } function handle(r: Result) { switch(r.type) { case 'success': /* ... */; break; case 'failure': /* ... */; break; default: return assertNever(r); // TS genererà un errore se appare un nuovo tipo } }

Esempi di errori reali a causa della mancanza di comprensione dei dettagli del tema.


Storia

Dopo aver ampliato il tipo "Result" con una nuova variante ('pending'), in diversi punti dell'applicazione i vecchi switch-case non gestivano questo caso. Di conseguenza, alcune interfacce hanno smesso di funzionare. L'errore è stato notato solo in produzione una settimana dopo il rilascio.


Storia

Il tentativo di utilizzare un'unione discriminata senza un discriminatore unico (il campo type duplicato in due tipi) ha portato a una "sfocatura" dei tipi: TypeScript ha smesso di restringere il tipo con precisione, ed è diventato possibile accedere a campi inesistenti senza errore di compilazione. Diversi bug critici sono stati inviati in produzione.


Storia

Nel progetto, il pattern matching è stato implementato tramite if-else su più campi invece di utilizzare un unico discriminatore esplicito. Questo ha complicato il passaggio alla verifica di exhaustiveness con la funzione never ed ha reso la leggibilità del codice più difficile — gli switch-case non funzionavano correttamente e le nuove varianti "rompevano" la logica esistente.