Przy włączonym strictNullChecks (to część strict) null i undefined mają własne typy. string nigdy nie może być null; wartość, której może brakować, musi to zaznaczyć w swoim typie, na przykład string | null, a TypeScript każe ci obsłużyć ten przypadek przed użyciem wartości.
Po sprawdzeniu kompilator wie, że name to string, więc .split jest dozwolone. Reszta strony omawia sposoby takiego sprawdzania i operatory, które je skracają.
strictNullChecks i błędy "possibly null"
Gdy typ zawiera null lub undefined, TypeScript nie pozwala używać wartości tak, jakby zawsze istniała:
index.ts(3,22): error TS18047: 'name' is possibly 'null'.
Wersja dla undefined to TS18048, 'x' is possibly 'undefined'. Bez strictNullChecks null i undefined są dozwolone w każdym typie i ten kod się kompiluje, a potem wywala się w czasie działania z TypeError, gdy tylko name okaże się null. Ta klasa błędów to główny powód, by trzymać strict włączone.
Skąd te typy biorą się w codziennym kodzie:
| Źródło | Typ |
|---|---|
arr.find(...) | T | undefined |
map.get(key) | V | undefined |
właściwość opcjonalna p?: T | T | undefined przy odczycie |
parametr opcjonalny x?: T | T | undefined wewnątrz funkcji |
str.match(re) | RegExpMatchArray | null |
JSON.parse(text) | any, więc nic nie jest sprawdzane |
Sprawdzanie null i undefined
Każde z poniższych sprawdzeń zawęża typ wewnątrz bloku. Wybierz to, które pasuje do tego, co chcesz wykluczyć.
| Sprawdzenie | Usuwa z typu |
|---|---|
x !== undefined | undefined |
x !== null | null |
x != null | null i undefined |
typeof x !== "undefined" | undefined |
if (x) | null i undefined, a do tego pomija wartości 0, "", false, NaN |
== null to jedyne miejsce, gdzie luźna równość jest idiomatyczna: jest prawdziwe dokładnie dla null i undefined, dla niczego więcej. Sprawdzenie prawdziwości potraktowałoby pusty string jako brak wartości, co często jest błędem.
Optional chaining: ?.
a?.b odczytuje b, jeśli a nie jest null ani undefined, a w przeciwnym razie zatrzymuje się i zwraca undefined. Ten sam operator działa dla indeksów, a?.[i], i dla wywołań, fn?.().
Typ bob.address?.city to string | undefined: optional chaining dodaje undefined do wyniku, więc zwykle łączy się go z ??. Zachowanie w czasie działania to zwykły JavaScript; szczegóły skracania obliczeń znajdziesz w optional chaining.
Podwójny znak zapytania: ??
a ?? b zwraca a, chyba że to null lub undefined, wtedy zwraca b. Zastępuje starszy idiom a || b, który odrzuca też 0, "", false i NaN:
| Lewa wartość | left || "d" | left ?? "d" |
|---|---|---|
null | "d" | "d" |
undefined | "d" | "d" |
0 | "d" | 0 |
"" | "d" | "" |
false | "d" | false |
NaN | "d" | NaN |
Na poziomie typów ?? usuwa null i undefined z lewej strony i łączy resztę w unię z prawą stroną, dlatego scores.get("Linus") ?? 0 można przypisać do number.
Nullish assignment: ??=
a ??= b przypisuje b do a tylko wtedy, gdy a to null lub undefined. Jego rodzeństwo, ||= i &&=, przypisuje, gdy lewa strona jest fałszywa lub prawdziwa.
retries: 0 przetrwa ??=, a pusty label zostaje zastąpiony przez ||=. Po opts.retries ??= 3 TypeScript zawęża opts.retries do number do końca funkcji.
Właściwości opcjonalne a | undefined
nickname?: string i nickname: string | undefined przy odczycie wyglądają tak samo, ale różnią się tym, czy klucz musi istnieć:
Użyj ?, gdy wywołujący mogą pominąć właściwość, a | undefined, gdy chcesz, żeby każdy wywołujący przekazał ją jawnie, nawet jeśli wartość to undefined. Opcja exactOptionalPropertyTypes (nie jest częścią strict) jeszcze bardziej zaostrza ?: wtedy nickname?: string odrzuca { nickname: undefined } i przyjmuje tylko brak klucza albo string. Opcjonalne parametry funkcji (x?: number) zachowują się jak właściwości opcjonalne: wewnątrz funkcji x ma typ number | undefined.
null czy undefined: czego używać
TypeScript nie wymusza wyboru, ale mieszanie obu w jednym projekcie oznacza, że każde sprawdzenie musi obsłużyć dwa przypadki. Popularna konwencja:
- Używaj
undefined(i właściwości opcjonalnych) dla "nieustawione" we własnych typach. To właśnie domyślnie produkuje JavaScript: brakujące właściwości, pominięte argumenty, nieudanefindiMap.get. - Przyjmuj
nulltam, gdzie daje ci go API:JSONnie maundefined, aString.prototype.matchi wiele metod DOM zwracanull. - Sprawdzaj przez
== null, gdy wartość może być jednym albo drugim.
Jedyną luką jest indeksowanie tablic: users[5] ma typ elementu, nawet gdy indeks wykracza poza zakres. Opcja noUncheckedIndexedAccess (nie jest częścią strict) dodaje | undefined do każdego dostępu przez indeks, więc kompilator wyłapuje także to.
Najczęściej zadawane pytania
Co oznacza podwójny znak zapytania w TypeScript?
a ?? b to operator nullish coalescing z JavaScriptu. Zwraca a, chyba że a to null lub undefined, wtedy zwraca b. W przeciwieństwie do || zachowuje inne wartości fałszywe, takie jak 0, "" i false. TypeScript usuwa null i undefined z typu lewej strony, więc gdy a ma typ string | undefined, a ?? "x" jest typu string.
Jak sprawdzić, czy wartość jest undefined w TypeScript?
Porównaj ją: if (value !== undefined) { ... }. TypeScript zawęża typ wewnątrz bloku. Aby jednym sprawdzeniem wykluczyć zarówno null, jak i undefined, użyj value != null (luźna równość), to jedyne miejsce, gdzie ==/!= jest idiomatyczne. Sprawdzenie prawdziwości (if (value)) też zawęża, ale pomija 0, "" i false.
Co oznacza "Object is possibly undefined"?
Błędy TS18048 ('x' is possibly 'undefined') i TS18047 ('x' is possibly 'null') albo TS2532 (Object is possibly 'undefined'), gdy wartość nie ma prostej nazwy, jak w getUser().address.city, pochodzą z strictNullChecks: typ zawiera undefined lub null, a kod używa wartości tak, jakby nie mogło jej brakować. Najpierw ją sprawdź, użyj optional chaining (x?.name), podaj wartość domyślną przez ?? albo zmień typ, jeśli wartości naprawdę nie może brakować.
Czym różni się null od undefined w TypeScript?
To dwa osobne typy, każdy z jedną wartością. JavaScript używa undefined dla rzeczy, które nigdy nie zostały ustawione (brakująca właściwość, pominięty argument, Map.get dla nieistniejącego klucza), a API używają null dla zamierzonego "braku wartości" (JSON, wiele metod DOM). TypeScript śledzi je osobno, więc string | null nie przyjmuje undefined. Wiele projektów wybiera undefined we własnym kodzie i przyjmuje null tylko na granicach.
Czy właściwość opcjonalna to to samo co | undefined?
Nie do końca. name?: string oznacza, że właściwości może w ogóle nie być, a jej odczyt daje string | undefined. name: string | undefined oznacza, że właściwość musi istnieć, nawet jeśli jej wartość to undefined. Z włączonym exactOptionalPropertyTypes name?: string przestaje też przyjmować jawną wartość undefined.