Перейти к содержимому
Теория · Объединения и литеральные типы · урок 3 из 8

null и undefined в объединениях

Вернёмся на парковку. Теперь на табличке написано «Машина — или место свободно». Прежде чем открыть багажник, нужно убедиться, что машина вообще стоит на месте: на пустом месте открывать нечего.

В программах тоже бывает, что значения нет. Функция ищет город пользователя и не всегда его находит. В JavaScript в таком случае часто возвращают null. В строгом режиме null — отдельный тип и в string не помещается. Поэтому TypeScript нужно сказать прямо: результат — строка или null.

TypeScript
function findCity(userId: number): string | null {
    if (userId === 1) {
        return "Kazan";
    }
    return null;
}

const city = findCity(2);
console.log(city.toUpperCase()); // ✗
TEXT
error TS18047: 'city' is possibly 'null'.

Давайте разберём пример по строкам:

  1. : string | null после скобок с параметрами говорит, что функция вернёт строку или null.
  2. Для пользователя 1 функция возвращает "Kazan", для остальных — null.
  3. const city = findCity(2) получает тип string | null: TypeScript берёт его из объявления функции.
  4. city.toUpperCase() вызывает ошибку: tsc сообщает, что в city может оказаться null.

Если бы этот JavaScript выполнился, программа упала бы с TypeError: Cannot read properties of null (reading 'toUpperCase'). TypeScript показывает эту ошибку до запуска.

Объединение с null

Запись string | null — обычное объединение типов. Только второй вариант в нём означает «значения нет». Это табличка «машина или пусто»: у значения такого типа нельзя вызвать метод строки, пока не исключён null.

С undefined всё устроено так же. Тип string | undefined значит «строка, а может, ничего не задали».

Эту проверку включает флаг strictNullChecks, который входит в строгий режим strict. Без него null и undefined помещаются в любой тип, tsc молчит, а ошибка снова возникает только при запуске.

Этот вопрос часто задают на собеседовании: «Что даёт strictNullChecks?» Ответ: null и undefined перестают подходить к другим типам, и компилятор требует учесть их явно.

Операторы ?. и ?? с типами

Операторы ?. и ?? из JavaScript работают и в TypeScript. Компилятор понимает, что они делают, и учитывает это в типе результата:

TypeScript
function findCity(userId: number): string | null {
    if (userId === 1) {
        return "Kazan";
    }
    return null;
}

const city = findCity(2);
const upper = city?.toUpperCase();
const shown = city ?? "—";
console.log(upper, shown); // undefined —

Наведите курсор на upper и shown в редакторе — подсказка покажет их типы. Разберём каждый оператор.

Оператор ?.

city?.toUpperCase() вызывает метод, только если в city не null и не undefined. Иначе всё выражение даёт undefined. Поэтому у upper тип string | undefined: строка, если город нашёлся, и undefined, если нет. Здесь город не нашёлся, и программа печатает undefined.

Оператор ??

city ?? "—" берёт правую часть, когда слева null или undefined. В любом случае получается строка: либо сам город, либо "—". Поэтому у shown тип string, и null из него исчез. Программа печатает —.

Частая ошибка

Результат ?. считают обычной строкой:

TypeScript
function findCity(userId: number): string | null {
    if (userId === 1) {
        return "Kazan";
    }
    return null;
}

const city = findCity(1);
const upper: string = city?.toUpperCase(); // ✗
TEXT
error TS2322: Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.

Оператор ?. не убирает «пустой» вариант, а меняет null на undefined. Вторая строка сообщения так и говорит: undefined в string не помещается. Неважно, что findCity(1) на деле вернёт строку: компилятор смотрит на тип. Чтобы получилась строка, добавьте значение по умолчанию через ??:

TypeScript
function findCity(userId: number): string | null {
    if (userId === 1) {
        return "Kazan";
    }
    return null;
}

const city = findCity(1);
const upper: string = city?.toUpperCase() ?? "—";
console.log(upper); // KAZAN

Главное

  • Тип string | null или string | undefined честно говорит, что значения может не быть.
  • Пока null не исключён, метод строки на таком значении вызвать нельзя: tsc сообщает 'city' is possibly 'null'.
  • Эту проверку включает strictNullChecks из строгого режима.
  • city?.toUpperCase() даёт string | undefined, а city ?? "—" — string.