Con strictNullChecks attivo (fa parte di strict), null e undefined sono tipi a sé. Una string non può mai essere null; un valore che potrebbe mancare deve dirlo nel suo tipo, come string | null, e TypeScript ti obbliga a gestire quel caso prima di usare il valore.
Dopo il controllo, il compilatore sa che name è una string, quindi .split è permesso. Il resto della pagina tratta i modi per fare quel controllo e gli operatori che lo accorciano.
strictNullChecks e gli errori "possibly null"
Quando un tipo include null o undefined, TypeScript si rifiuta di usare il valore come se ci fosse sempre:
index.ts(3,22): error TS18047: 'name' is possibly 'null'.
La versione con undefined è TS18048, 'x' is possibly 'undefined'. Senza strictNullChecks, null e undefined sono ammessi in ogni tipo e questo codice compila, poi va in crash a runtime con un TypeError la prima volta che name è null. Quella categoria di bug è il motivo principale per tenere attivo strict.
Da dove arrivano questi tipi nel codice di tutti i giorni:
| Origine | Tipo |
|---|---|
arr.find(...) | T | undefined |
map.get(key) | V | undefined |
proprietà opzionale p?: T | T | undefined in lettura |
parametro opzionale x?: T | T | undefined dentro la funzione |
str.match(re) | RegExpMatchArray | null |
JSON.parse(text) | any, quindi non si controlla nulla |
Controllare null e undefined
Ogni controllo qui sotto restringe il tipo dentro il blocco. Scegli quello che corrisponde a ciò che vuoi escludere.
| Controllo | Toglie dal tipo |
|---|---|
x !== undefined | undefined |
x !== null | null |
x != null | null e undefined |
typeof x !== "undefined" | undefined |
if (x) | null e undefined, e salta anche i valori 0, "", false, NaN |
== null è l'unico caso in cui l'uguaglianza non stretta è idiomatica: è vera esattamente per null e undefined, nient'altro. Un controllo di truthiness avrebbe considerato mancante la stringa vuota, il che spesso è un bug.
Optional chaining: ?.
a?.b legge b se a non è null né undefined, altrimenti si ferma e restituisce undefined. Lo stesso operatore funziona con gli indici, a?.[i], e con le chiamate, fn?.().
Il tipo di bob.address?.city è string | undefined: l'optional chaining aggiunge undefined al risultato, quindi di solito lo abbini a ??. Il comportamento a runtime è JavaScript puro; vedi optional chaining per i dettagli sul cortocircuito.
Il doppio punto interrogativo: ??
a ?? b restituisce a a meno che sia null o undefined, nel qual caso restituisce b. Sostituisce il vecchio idioma a || b, che scarta anche 0, "", false e NaN:
| Valore a sinistra | left || "d" | left ?? "d" |
|---|---|---|
null | "d" | "d" |
undefined | "d" | "d" |
0 | "d" | 0 |
"" | "d" | "" |
false | "d" | false |
NaN | "d" | NaN |
Sul piano dei tipi, ?? toglie null e undefined dal lato sinistro e unisce il resto con il lato destro, ed è per questo che scores.get("Linus") ?? 0 si può assegnare a un number.
Nullish assignment: ??=
a ??= b assegna b ad a solo quando a è null o undefined. I suoi fratelli ||= e &&= assegnano quando il lato sinistro è falsy o truthy.
retries: 0 sopravvive a ??=, mentre la label vuota viene sostituita da ||=. Dopo opts.retries ??= 3, TypeScript restringe opts.retries a number per il resto della funzione.
Proprietà opzionali vs | undefined
nickname?: string e nickname: string | undefined si leggono allo stesso modo, ma differiscono sul fatto che la chiave debba esistere o no:
Usa ? quando chi chiama può omettere la proprietà, e | undefined quando vuoi che ogni chiamante la passi esplicitamente, anche se il valore è undefined. L'opzione exactOptionalPropertyTypes (non fa parte di strict) rende ? ancora più rigido: nickname?: string rifiuta allora { nickname: undefined } e accetta solo una chiave mancante o una stringa. I parametri opzionali delle funzioni (x?: number) si comportano come le proprietà opzionali: dentro la funzione x è number | undefined.
null o undefined: quale usare
TypeScript non impone una scelta, ma mescolarli entrambi in un codebase significa che ogni controllo deve gestire due casi. Una convenzione comune:
- Usa
undefined(e le proprietà opzionali) per "non impostato" nei tuoi tipi. È ciò che JavaScript produce di default: proprietà mancanti, argomenti omessi,findeMap.getche non trovano nulla. - Accetta
nulldove te lo dà un'API:JSONnon haundefined,String.prototype.matche molti metodi del DOM restituiscononull. - Controlla con
== nullquando un valore potrebbe essere l'uno o l'altro.
L'indicizzazione degli array è l'unica lacuna: users[5] ha il tipo dell'elemento anche quando l'indice è fuori intervallo. L'opzione noUncheckedIndexedAccess (non fa parte di strict) aggiunge | undefined a ogni accesso tramite indice, così il compilatore intercetta anche quel caso.
Domande frequenti
Cosa significa il doppio punto interrogativo in TypeScript?
a ?? b è l'operatore di nullish coalescing di JavaScript. Restituisce a a meno che a sia null o undefined, nel qual caso restituisce b. A differenza di ||, mantiene gli altri valori falsy come 0, "" e false. TypeScript toglie null e undefined dal tipo del lato sinistro, quindi quando a è string | undefined, a ?? "x" è una string.
Come controllo se un valore è undefined in TypeScript?
Confrontalo: if (value !== undefined) { ... }. TypeScript restringe il tipo dentro il blocco. Per escludere sia null sia undefined con un solo controllo, usa value != null (uguaglianza non stretta), l'unico caso in cui ==/!= è idiomatico. Anche un controllo di truthiness (if (value)) restringe il tipo, ma salta 0, "" e false.
Cosa significa "Object is possibly undefined"?
Gli errori TS18048 ('x' is possibly 'undefined') e TS18047 ('x' is possibly 'null'), o TS2532 (Object is possibly 'undefined') quando il valore non ha un nome semplice, come in getUser().address.city, vengono da strictNullChecks: il tipo include undefined o null, e il codice usa il valore come se non potesse esserlo. Controllalo prima, usa l'optional chaining (x?.name), fornisci un valore predefinito con ??, oppure cambia il tipo se il valore non può davvero mancare.
Che differenza c'è tra null e undefined in TypeScript?
Sono due tipi separati con un solo valore ciascuno. JavaScript usa undefined per le cose mai impostate (una proprietà mancante, un argomento omesso, Map.get su una chiave che non c'è) e le API usano null per un "nessun valore" intenzionale (JSON, molti metodi del DOM). TypeScript li tiene distinti, quindi string | null non accetta undefined. Molti codebase scelgono undefined per il proprio codice e accettano null solo ai confini.
Una proprietà opzionale è la stessa cosa di | undefined?
Non proprio. name?: string significa che la proprietà può mancare del tutto, e leggerla dà string | undefined. name: string | undefined significa che la proprietà deve esserci, anche se il suo valore è undefined. Con exactOptionalPropertyTypes attivo, name?: string smette anche di accettare un valore undefined esplicito.