Avatar ·

Как объявить тип, допускающий значение null или undefined в TypeScript?

Привет! Я только начал изучать TypeScript и столкнулся с проблемой. У меня есть функция, которая должна принимать число, но иногда я хочу передать ей null или просто ничего не передавать (undefined). В обычном JavaScript это работает без проблем, но TypeScript ругается. Вот пример моего кода: ```typescript function greetUser(age: number) { console.log(`Возраст: ${age}`); } // Хочу передать null, если возраст неизвестен greetUser(null); // Ошибка: Argument of type 'null' is not assignable to parameter of type 'number'. // Или вообще не передавать аргумент greetUser(); // Ошибка: Expected 1 arguments, but got 0. ``` Я пробовал использовать `age: number | null`, но тогда функция не позволяет передать undefined. А если пишу `age: number | undefined`, то не работает с null. Как правильно сделать, чтобы функция принимала и число, и null, и undefined? Может быть, есть какой-то специальный тип в TypeScript для этого? Видел в документации что-то про `?` перед двоеточием, но не понял, как это применить. Объясните, пожалуйста, как объявить такой "nullable" тип, чтобы код работал без ошибок и был типобезопасным. Заранее спасибо! <<>>

Объединение типов (Union Types) — основной способ

В TypeScript для объявления типа, который может принимать null или undefined, используется механизм объединения типов (union types). Вы указываете несколько возможных типов через вертикальную черту |.

Чтобы ваша функция принимала number, null и undefined, нужно написать так:

function greetUser(age: number | null | undefined) {
  if (age === null || age === undefined) {
    console.log('Возраст неизвестен');
  } else {
    console.log(`Возраст: ${age}`);
  }
}

// Теперь все работает без ошибок
greetUser(25);       // Возраст: 25
greetUser(null);     // Возраст неизвестен
greetUser(undefined); // Возраст неизвестен

Сокращение с помощью "?" (опциональный параметр)

Если вы хотите, чтобы параметр мог отсутствовать (то есть быть undefined при вызове без аргумента), нужно использовать опциональный параметр — знак вопроса ? перед двоеточием:

function greetUser(age?: number) {
  // age может быть number или undefined
  if (age === undefined) {
    console.log('Возраст неизвестен');
  } else {
    console.log(`Возраст: ${age}`);
  }
}

greetUser(25);  // Возраст: 25
greetUser();    // Возраст: неизвестен (undefined)
// greetUser(null); // Ошибка! null не допускается

Обратите внимание: ? автоматически добавляет undefined в тип, но null — нет. Если нужно и то, и другое, комбинируйте:

function greetUser(age?: number | null) {
  // age: number | undefined | null
}

strictNullChecks — почему это важно

В TypeScript есть флаг компилятора strictNullChecks (обычно включен в strict режиме). Когда он активен, null и undefined не являются частью других типов автоматически. Это заставляет вас явно указывать их в объединении, что делает код безопаснее.

Если отключить эту опцию (не рекомендуется), то можно писать age: number и передавать null — но тогда вы теряете все преимущества типизации.

Практические примеры

  • Возврат значений: функция может вернуть string | null, если поиск не дал результата.
  • Свойства объектов: user: { name: string; age?: number | null } — возраст может отсутствовать или быть null.
  • Тип "maybe": часто используют type MaybeNumber = number | null | undefined; для переиспользования.

Важно: сужение типа (type narrowing)

Когда вы работаете с объединённым типом, TypeScript заставляет вас проверять, какое именно значение пришло. Используйте условные операторы или typeof:

function process(age: number | null | undefined) {
  // Без проверки нельзя использовать age как число
  // age.toFixed(2); // Ошибка!
  
  if (typeof age === 'number') {
    // Внутри блока age точно number
    return age.toFixed(2);
  }
  return 'Не число';
}

Такой подход предотвращает ошибки времени выполнения, когда вы случайно вызываете методы на null или undefined.

Итог

  • Для null и undefined используйте тип | null | undefined.
  • Для опциональных параметров используйте ? — он добавляет undefined.
  • Всегда проверяйте значения перед использованием — это TypeScript-вей.
  • Не отключайте strictNullChecks — это главный защитник от багов с null.
<<>>

Использование дженериков для создания универсального nullable-типа

Если в вашем проекте часто встречаются nullable-типы, можно создать обобщённый тип-помощник. Это сделает код чище и уменьшит повторения:

type Nullable<T> = T | null | undefined;

// Использование
function greetUser(age: Nullable<number>) {
  if (age == null) { // проверка на null и undefined одновременно
    console.log('Возраст неизвестен');
  } else {
    console.log(`Возраст: ${age}`);
  }
}

Обратите внимание на age == null (с двумя равно) — это JavaScript-трюк, который проверяет и null, и undefined одновременно, что очень удобно в таких случаях.

Альтернатива: Non-null assertion operator (!)

Иногда вы точно знаете, что значение не null/undefined, но TypeScript не может это вывести. Используйте оператор ! (non-null assertion) для подавления ошибки:

function getLength(str: string | null): number {
  // Мы уверены, что str не null, хотя TypeScript не уверен
  return str!.length; // "!" говорит: "доверься мне, это не null"
}

Предупреждение: используйте ! только когда абсолютно уверены, иначе рискуете получить runtime-ошибку. Это костыль, а не решение.

Когда использовать что

  • Union types (|) — стандартный, безопасный подход.
  • Nullable<T> — для частого повторения.
  • Опциональные параметры (?) — для отсутствующих аргументов.
  • Non-null assertion (!) — только в крайних случаях, когда вы контролируете логику.

Выбирайте первый вариант как основной, а остальные — по необходимости.