Проверка на null и сравнение
Вернёмся на сортировочную ленту. Иногда лоток доезжает до арки пустым. Прежде чем решать, конверт в нём или банка, работник смотрит, лежит ли в лотке хоть что-нибудь. Пустой лоток уходит в сторону, а дальше по ленте едут только настоящие посылки.
В программе пустой лоток — это null или undefined. Вы уже знаете, что у значения типа string | null нельзя вызвать метод строки, пока не исключён null:
function greet(nick: string | null) {
console.log("Hi, " + nick.toUpperCase()); // ✗
}error TS18047: 'nick' is possibly 'null'.Для null и undefined проверка typeof не нужна. TypeScript сужает тип и после двух других проверок: условия на истинность и сравнения.
Проверка на истинность
Условие if (nick) пропускает в ветку только истинные значения. null и undefined в JavaScript ложные, поэтому tsc убирает их из типа внутри if:
function greet(nick: string | null) {
if (nick) {
console.log("Hi, " + nick.toUpperCase());
} else {
console.log("Hi, guest");
}
}
greet("ann"); // Hi, ANN
greet(null); // Hi, guest
greet(""); // Hi, guestДавайте разберём пример по строкам:
- Внутри
ifуnickтипstring:nullсюда не попадает, иtoUpperCaseкомпилируется. greet("ann")печатаетHi, ANN.greet(null)уходит вelseи печатаетHi, guest.greet("")тоже печатаетHi, guest: пустая строка в JavaScript ложная.
Последний вызов важен. В else у nick тип string | null, а не null: туда попадает и пустая строка. Условие на истинность отсекает все ложные значения JavaScript сразу:
nullиundefined;false;0,0nиNaN;- пустую строку
"".
Здесь аналогия с лотком перестаёт работать. Пустая строка и ноль — не пустой лоток, а настоящие посылки, но проверка на истинность отправляет их в сторону вместе с пустыми лотками.
Сравнение через = и !
Когда нужно отсечь только null, его сравнивают явно. TypeScript понимает и ===, и !==:
function badge(unread: number | null) {
if (unread !== null) {
return "unread: " + unread;
}
return "loading";
}
function same(a: string | number, b: string | boolean) {
if (a === b) {
return a.toUpperCase();
}
return "-";
}
console.log(badge(0), same("ok", "ok")); // unread: 0 OKПрограмма печатает unread: 0 OK. Разберём обе функции.
Сравнение с null
unread !== null убирает из типа ровно null. Внутри if у unread тип number, и ноль остаётся настоящим числом, поэтому badge(0) возвращает unread: 0. После return у unread остался тип null.
С undefined пишут так же: x !== undefined. А вот typeof x === "object" для этой цели не годится: в JavaScript typeof null тоже даёт "object", и tsc об этом помнит, поэтому null в такой ветке останется.
Сравнение двух значений
Сравнивать можно и два значения с типами-объединениями. Если a === b истинно, в a и b лежит одно и то же значение, а значит, у него тип, который есть в обоих объединениях:
- у
aвариантыstringиnumber; - у
bвариантыstringиboolean; - общий вариант у них один —
string.
Поэтому внутри if tsc считает строками и a, и b, и a.toUpperCase() компилируется.
Частая ошибка
Проверили число на истинность, чтобы отсечь null:
function badge(unread: number | null) {
if (unread) { // ✗
return "unread: " + unread;
}
return "loading";
}
console.log(badge(0));tsc молчит: внутри if у unread тип number, всё законно. Но программа печатает loading, хотя количество сообщений известно — оно равно нулю. Ноль ложный, и условие отправило его в ту же ветку, что и null. Когда 0 или пустая строка — нормальные значения, проверяйте unread !== null, как в примере выше. Эту ловушку любят проверять на собеседованиях.
Главное
if (x)убирает из типаnullиundefined, но отсекает и другие ложные значения:0,"",NaN,false.x !== nullубирает из типа ровноnull, а ноль и пустая строка остаются в ветке.- Если
a === b, внутриifу обоих значений тип, общий для их объединений. - tsc не предупредит, что
if (unread)отправил ноль в ветку «значения нет»: это ошибка в логике, а не в типах.