Uno union type elenca delle alternative con |: un valore di tipo string | number è una stringa oppure un numero. Le union sono il modo in cui TypeScript descrive valori che possono legittimamente assumere più di una forma, e il compilatore ti obbliga a verificare quale forma hai prima di usare qualcosa di specifico.
Dentro ogni ramo del controllo typeof, id ha un solo tipo. Questo passaggio si chiama narrowing, ed è ciò che rende le union utilizzabili in pratica.
Sono ammessi solo i membri comuni
Prima del narrowing, puoi usare solo ciò che supportano tutti i membri della union. toString() esiste sia sulle stringhe sia sui numeri, quindi va bene; toUpperCase() esiste solo sulle stringhe:
index.ts(3,16): error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.
Property 'toUpperCase' does not exist on type 'number'.
La seconda riga indica il membro a cui manca la proprietà. La stessa regola vale nella direzione opposta: un valore string | number non si può passare a un parametro tipizzato string (TS2345), perché potrebbe essere un numero. Una union accetta più valori e, in cambio, ti lascia fare meno con essi finché non controlli.
Restringere una union
Il narrowing usa normali controlli JavaScript. TypeScript segue il flusso di controllo e toglie i membri man mano che vengono esclusi, quindi dopo l'ultimo controllo resta un solo membro:
| Controllo | Restringe | Adatto per |
|---|---|---|
typeof x === "string" | al primitivo | string, number, boolean, bigint, symbol, undefined, function |
x === null, x === "a" | al valore confrontato | null, undefined, membri letterali |
Array.isArray(x) | al membro array | array |
x instanceof Date | alla classe | istanze di classi |
"meow" in x | ai membri che hanno la proprietà | tipi oggetto |
x.kind === "circle" | al membro con quell'etichetta | discriminated union |
isCat(x) (restituisce x is Cat) | a ciò che dice la funzione | qualsiasi cosa, logica personalizzata |
L'elenco completo delle forme di narrowing è nella pagina sul type narrowing.
Union di tipi letterali
Una union di valori letterali è un insieme chiuso di valori ammessi. È la union più comune nel codice reale:
type Status = "idle" | "loading" | "success" | "error";
type Dice = 1 | 2 | 3 | 4 | 5 | 6;
type Toggle = "on" | "off" | boolean; // boolean is itself true | false
let current: Status = "idle";
current = "loading"; // fine
current = "finished"; // error TS2322: Type '"finished"' is not assignable to type 'Status'.
Il confronto con un letterale restringe il tipo: dopo if (current === "error"), il ramo else sa che current è uno degli altri tre. In molte codebase le union di letterali sostituiscono gli enum; vedi i literal types per as const e per come ricavare una union di questo tipo da un array.
Union di tipi oggetto
Quando i membri sono tipi oggetto, le proprietà che hanno tutti in comune sono disponibili direttamente. Per le altre, verifica con in che la proprietà esista:
Per le union di più forme di oggetti, il pattern più pulito è una proprietà letterale condivisa come kind: "cat" / kind: "fish". Controllare quell'unica proprietà restringe l'intero oggetto, e su uno switch basato su di essa si può verificare l'esaustività. Questo pattern si chiama discriminated union.
Array e union
La posizione delle parentesi cambia del tutto il significato:
| Tipo | Significa | Valore di esempio |
|---|---|---|
(string | number)[] | un array i cui elementi sono ciascuno una stringa o un numero | [1, "two", 3] |
string[] | number[] | un array di sole stringhe, o un array di soli numeri | ["a", "b"] |
string | number[] | una stringa, o un array di numeri (| lega meno di []) | "text" |
Quando iteri su un (string | number)[], ogni elemento è la union e va ristretto, come nella callback di reduce qui sopra. Anche metodi come map e filter funzionano su uno string[] | number[], con la callback che riceve string | number.
Union con null e undefined
La union più comune in assoluto è "un valore o niente": string | null, User | undefined. È ciò che restituiscono Array.prototype.find e Map.prototype.get, e una proprietà opzionale name?: string si legge come string | undefined. Gestirle con ?., ?? e i controlli su null ha una pagina dedicata: null e undefined.
Per togliere membri da una union esistente a livello di tipi, usa le utility integrate: Exclude<"a" | "b" | "c", "a"> è "b" | "c", e NonNullable<string | null> è string.
Domande frequenti
Cos'è uno union type in TypeScript?
Un tipo composto da più alternative unite con |. Un valore di tipo string | number può essere una stringa o un numero. Il compilatore ti permette di usare solo ciò che tutti i membri hanno in comune, finché non restringi il valore a un solo membro con un controllo come typeof value === "string".
Perché TypeScript dice che una proprietà non esiste su uno union type?
Perché almeno un membro della union non ce l'ha. L'errore TS2339, per esempio Property 'toUpperCase' does not exist on type 'string | number', significa che il valore potrebbe essere un numero, che non ha toUpperCase. Restringi prima il tipo (typeof, in, Array.isArray, instanceof o un controllo sul discriminante), poi usa la proprietà specifica del membro.
Come dichiaro un array che contiene più di un tipo?
Metti la union tra parentesi: (string | number)[] o Array<string | number>, dove ogni elemento può essere dell'uno o dell'altro tipo. string[] | number[] è diverso: l'intero array è tutto di stringhe o tutto di numeri. Senza parentesi, string | number[] significa una stringa o un array di numeri.
Che differenza c'è tra uno union type e un intersection type?
Una union A | B è un valore che è uno dei tipi, quindi puoi usare solo ciò che hanno in comune. Un'intersezione A & B è un valore che è entrambi insieme, quindi ha tutti i membri di entrambi. Per i tipi oggetto, A | B accetta più valori e A & B richiede più proprietà.
Come verifico di che tipo è un valore union?
Usa un controllo a runtime che TypeScript capisce: typeof x === "string" per i primitivi, Array.isArray(x) per gli array, x instanceof Date per le classi, "prop" in x per le forme degli oggetti, oppure x.kind === "circle" quando i membri condividono un'etichetta letterale. Per una logica personalizzata, scrivi una funzione type guard che restituisce x is T.