При включённом strictNullChecks (он входит в strict) null и undefined становятся собственными типами. string никогда не может быть null; значение, которое может отсутствовать, должно указывать это в своём типе, например string | null, и TypeScript заставляет обработать этот случай, прежде чем использовать значение.
После проверки компилятор знает, что name имеет тип string, поэтому .split разрешён. Дальше на странице разобраны способы такой проверки и операторы, которые делают её короче.
strictNullChecks и ошибки «possibly null»
Если тип включает null или undefined, TypeScript отказывается использовать значение так, будто оно есть всегда:
index.ts(3,22): error TS18047: 'name' is possibly 'null'.
Вариант с undefined это TS18048, 'x' is possibly 'undefined'. Без strictNullChecks null и undefined допустимы в любом типе, и этот код компилируется, а затем падает во время выполнения с TypeError, как только name окажется null. Этот класс ошибок и есть главная причина держать strict включённым.
Откуда эти типы берутся в повседневном коде:
| Источник | Тип |
|---|---|
arr.find(...) | T | undefined |
map.get(key) | V | undefined |
необязательное свойство p?: T | T | undefined при чтении |
необязательный параметр x?: T | T | undefined внутри функции |
str.match(re) | RegExpMatchArray | null |
JSON.parse(text) | any, поэтому ничего не проверяется |
Проверка на null и undefined
Каждая проверка ниже сужает тип внутри блока. Выберите ту, которая исключает то, что вам нужно.
| Проверка | Что убирает из типа |
|---|---|
x !== undefined | undefined |
x !== null | null |
x != null | null и undefined |
typeof x !== "undefined" | undefined |
if (x) | null и undefined, а заодно пропускает значения 0, "", false, NaN |
== null это единственное место, где нестрогое равенство идиоматично: оно истинно ровно для null и undefined и ни для чего больше. Проверка на истинность сочла бы пустую строку отсутствующим значением, а это часто ошибка.
Опциональная цепочка: ?.
a?.b читает b, если a не null и не undefined, а иначе останавливается и возвращает undefined. Тот же оператор работает для индексов, a?.[i], и для вызовов, fn?.().
Тип bob.address?.city это string | undefined: опциональная цепочка добавляет undefined к результату, поэтому её обычно сочетают с ??. Поведение во время выполнения это обычный JavaScript; подробности о прерывании вычисления см. в разделе опциональная цепочка.
Двойной вопросительный знак: ??
a ?? b возвращает a, если оно не null и не undefined, иначе возвращает b. Он заменяет старую идиому a || b, которая отбрасывает ещё и 0, "", false и NaN:
| Левое значение | left || "d" | left ?? "d" |
|---|---|---|
null | "d" | "d" |
undefined | "d" | "d" |
0 | "d" | 0 |
"" | "d" | "" |
false | "d" | false |
NaN | "d" | NaN |
На уровне типов ?? убирает null и undefined из левой части и объединяет остаток с правой частью, поэтому scores.get("Linus") ?? 0 можно присвоить в number.
Присваивание при null: ??=
a ??= b присваивает b переменной a, только если a равно null или undefined. Родственные операторы ||= и &&= присваивают, когда левая часть ложна или истинна.
retries: 0 переживает ??=, а пустой label заменяется через ||=. После opts.retries ??= 3 TypeScript сужает opts.retries до number до конца функции.
Необязательные свойства и | undefined
nickname?: string и nickname: string | undefined при чтении ведут себя одинаково, но различаются тем, обязан ли ключ существовать:
Используйте ?, когда вызывающий код может опустить свойство, и | undefined, когда нужно, чтобы каждый вызывающий код передавал его явно, даже если значение undefined. Параметр exactOptionalPropertyTypes (не входит в strict) ещё сильнее ужесточает ?: тогда nickname?: string отвергает { nickname: undefined } и принимает только отсутствующий ключ или строку. Необязательные параметры функций (x?: number) ведут себя как необязательные свойства: внутри функции x имеет тип number | undefined.
null или undefined: что использовать
TypeScript не заставляет выбирать, но смешение обоих в одной кодовой базе означает, что каждая проверка должна обрабатывать два случая. Распространённое соглашение:
- Используйте
undefined(и необязательные свойства) для «не задано» в собственных типах. Именно это JavaScript выдаёт по умолчанию: отсутствующие свойства, пропущенные аргументы, промахиfindиMap.get. - Принимайте
nullтам, где его отдаёт API: вJSONнетundefined, аString.prototype.matchи многие методы DOM возвращаютnull. - Проверяйте через
== null, если значение может быть любым из двух.
Единственный пробел это индексация массивов: users[5] имеет тип элемента, даже если индекс вне диапазона. Параметр noUncheckedIndexedAccess (не входит в strict) добавляет | undefined к каждому обращению по индексу, чтобы компилятор ловил и это.
Часто задаваемые вопросы
Что означает двойной вопросительный знак в TypeScript?
a ?? b это оператор нулевого слияния (nullish coalescing) из JavaScript. Он возвращает a, если a не null и не undefined, иначе возвращает b. В отличие от ||, он сохраняет другие ложные значения вроде 0, "" и false. TypeScript убирает null и undefined из типа левой части, поэтому если a имеет тип string | undefined, то a ?? "x" это string.
Как проверить, что значение undefined, в TypeScript?
Сравните его: if (value !== undefined) { ... }. TypeScript сужает тип внутри блока. Чтобы исключить и null, и undefined одной проверкой, используйте value != null (нестрогое равенство): это единственное место, где ==/!= считается идиоматичным. Проверка на истинность (if (value)) тоже сужает тип, но пропускает 0, "" и false.
Что означает "Object is possibly undefined"?
Ошибки TS18048 ('x' is possibly 'undefined') и TS18047 ('x' is possibly 'null') или TS2532 (Object is possibly 'undefined'), когда у значения нет простого имени, как в getUser().address.city, возникают из-за strictNullChecks: тип включает undefined или null, а код использует значение так, будто его не может не быть. Сначала проверьте его, используйте опциональную цепочку (x?.name), задайте значение по умолчанию через ?? или измените тип, если значение действительно не может отсутствовать.
Чем null отличается от undefined в TypeScript?
Это два отдельных типа, у каждого одно значение. JavaScript использует undefined для того, что никогда не задавалось (отсутствующее свойство, пропущенный аргумент, Map.get по отсутствующему ключу), а API используют null для намеренного «нет значения» (JSON, многие методы DOM). TypeScript отслеживает их отдельно, поэтому string | null не принимает undefined. Многие кодовые базы выбирают undefined для собственного кода и принимают null только на границах.
Необязательное свойство это то же самое, что | undefined?
Не совсем. name?: string означает, что свойство может полностью отсутствовать, а его чтение даёт string | undefined. name: string | undefined означает, что свойство должно присутствовать, даже если его значение undefined. Со включённым exactOptionalPropertyTypes name?: string ещё и перестаёт принимать явное значение undefined.