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 вклю