Nullable в TypeScript: как объявить тип, который может быть null?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

TypeScript и его вечный вопрос: как сделать тип nullable?

Привет, коллега! Если ты когда-нибудь писал на TypeScript, то наверняка сталкивался с ситуацией, когда функция может вернуть значение, а может и не вернуть. Или переменная инициализируется не сразу. И тут возникает сакраментальный вопрос: как объявить тип, который может быть null или undefined?

Этот вопрос — один из самых популярных на Stack Overflow (десятки тысяч просмотров!), и неспроста. TypeScript строг, но справедлив. Он не позволяет нам просто так взять и присвоить null переменной с типом string. И это прекрасно, потому что спасает от кучи багов. Но как быть, когда nullable — это часть бизнес-логики? Давай разбираться.

В этой статье мы не просто дадим сухой ответ. Мы пройдемся от базы до продвинутых техник, разберем частые ошибки и узнаем, как писать код, который будет безопасным и элегантным. Погнали!

Основы: Union Types и Null

Самый простой и прямой способ сделать тип nullable — это использовать union type (объединение типов). Если переменная может быть строкой или null, мы пишем так:

let userName: string | null = null;
userName = "Алиса"; // Ок
userName = null;    // Тоже ок

То же самое работает с undefined. В TypeScript null и undefined — это разные типы (если не включен флаг --strictNullChecks, но о нем чуть позже).

let age: number | undefined;
age = 25;
age = undefined; // Работает

Важно: если ты используешь | null или | undefined, TypeScript заставит тебя проверять значение перед использованием. Попытка вызвать метод у потенциально null-объекта приведет к ошибке компиляции. Это и есть та самая "безопасность", за которую мы любим TS.

Пример из реальной жизни: поиск пользователя

interface User {
  id: number;
  name: string;
}

function findUser(id: number): User | null {
  // ...логика поиска
  if (id === 1) {
    return { id: 1, name: "Иван" };
  }
  return null;
}

const user = findUser(2);
// user.name; // Ошибка! 'user' возможно null
if (user) {
  console.log(user.name); // Теперь безопасно
}

Видишь? Компилятор сам подсказывает тебе: "Эй, проверь, а то упадешь!". Это экономит часы отладки.

Опциональные параметры и свойства (Optional Chaining)

Часто nullable бывают не только переменные, но и поля объектов или аргументы функций. Для этого в TypeScript есть специальный синтаксис — optional modifier (знак вопроса ?).

Опциональные параметры функции

function greet(name?: string) {
  if (name) {
    return `Привет, ${name}!`;
  }
  return "Привет, незнакомец!";
}

console.log(greet("Петя")); // Привет, Петя!
console.log(greet());       // Привет, незнакомец!

Тип параметра name автоматически становится string | undefined. Обрати внимание: если параметр опциональный, его можно не передавать, и TS не ругнется.

Опциональные свойства интерфейсов

interface Config {
  url: string;
  port?: number; // может быть number или undefined
}

const config1: Config = { url: "http://localhost" }; // port отсутствует
const config2: Config = { url: "http://localhost", port: 3000 };

При обращении к config1.port TypeScript скажет, что это number | undefined. И это правильно — мы не знаем, задан ли порт.

Опциональная цепочка (Optional Chaining) — ваш лучший друг

С появлением ES2020 и TypeScript 3.7 у нас есть элегантный способ безопасно обращаться к вложенным свойствам, которые могут быть null или undefined. Это называется optional chaining (опциональная цепочка).

interface Address {
  street: string;
  city?: string;
}

interface Person {
  name: string;
  address?: Address;
}

const person: Person = { name: "Ольга" };

// Без optional chaining пришлось бы писать:
if (person.address && person.address.city) {
  console.log(person.address.city);
}

// С optional chaining:
console.log(person.address?.city); // undefined, но без ошибки!

Оператор ?. проверяет, не является ли левая часть выражения null или undefined. Если является — цепочка обрывается и возвращается undefined. Если нет — идет дальше. Гениально и безопасно!

Еще есть nullish coalescing operator (??), который часто идет в паре с optional chaining. Он возвращает правую часть, только если левая — null или undefined (в отличие от ||, который срабатывает на falsy значения типа 0 или '').

const city = person.address?.city ?? "Город не указан";
console.log(city); // "Город не указан"

Null vs Undefined: в чем разница и что выбрать?

О, это извечный холивар! В JavaScript null — это "ничего, пустое значение", которое программист установил явно. undefined — это "значение еще не присвоено". TypeScript наследует эту логику, но позволяет нам выбирать.

Рекомендация сообщества TypeScript:

  • Используйте undefined для опциональных параметров и свойств (синтаксис с ?).
  • Используйте null для явного указания "значение отсутствует" (например, когда API возвращает null для пустого поля).
  • Не смешивайте их без необходимости — это запутывает код.

Пример: если ты пишешь функцию, которая возвращает результат поиска, лучше вернуть null, а не undefined, чтобы явно сказать: "Я искал, но не нашел". А для необязательных параметров используй undefined (через ?).

Глобальная настройка: strictNullChecks

Если ты только начинаешь с TypeScript, возможно, ты не включал флаг strictNullChecks в tsconfig.json. По умолчанию (без strict mode) TypeScript позволяет присваивать null и undefined любому типу. Это наследие старых версий, и я настоятельно рекомендую от этого отказаться.

// tsconfig.json
{
  "compilerOptions": {
    "strictNullChecks": true, // Включаем!
    "strict": true            // Или просто включить strict, который активирует всё
  }
}

Когда strictNullChecks вклю

Блоги

Рекомендаций по книгам