К содержанию

Варианты значений и проверки

Допустимые варианты, отсутствующие данные и понятная обработка результата.

также в основах веб-разработки

На этой странице

У мероприятия может быть два формата: онлайн и очно. Результат поиска может быть найденным объектом или undefined. TypeScript позволяет описать такие варианты, а обычные проверки JavaScript помогают понять, какой из них сейчас получен.

Перечисляем допустимые значения

Если записать формат просто как string, подойдёт любая строка, включая опечатку. Перечислим только нужные варианты:

main.ts
type EventFormat = "online" | "offline";
 
function getFormatLabel(format: EventFormat): string {
  if (format === "online") {
    return "Онлайн";
  }
 
  return "Очно";
}
 
console.log(getFormatLabel("online")); // "Онлайн"
// getFormatLabel("onlien"); // Ошибка: такого варианта нет.

"online" здесь – литеральный тип: допустима именно эта строка. Знак | объединяет варианты и читается как «или». Весь EventFormat означает «строка online или строка offline».

Так же можно описать статус заявки, категорию товара или допустимый размер кнопки. Не нужно перечислять варианты там, где текст действительно произвольный: имя человека остаётся string.

Уточняем тип проверкой

Иногда входом может быть строка или число:

main.ts
function formatTicketCode(code: string | number): string {
  if (typeof code === "number") {
    return code.toFixed(0);
  }
 
  return code.trim().toUpperCase();
}

Внутри if TypeScript знает, что code – число, и разрешает числовой метод toFixed. После return остаётся строковый вариант, у которого есть trim. Это сужение типа: проверка уменьшает число возможных вариантов.

Сам typeof работает при выполнении JavaScript. TypeScript читает эту проверку и использует её смысл при анализе кода.

Отсутствующее значение тоже нужно учесть

Свойство с ? может отсутствовать:

main.ts
type EventCard = {
  name: string;
  description?: string;
};
 
function getDescription(event: EventCard): string {
  return event.description ?? "Описание появится позже";
}
 
console.log(getDescription({ name: "Лекция" }));
// "Описание появится позже"

При чтении description возможны string или undefined. Оператор ?? подставляет запасное значение для null и undefined. Пустую строку и число 0 он сохраняет. Оператор || подставил бы замену и для них, что подходит не каждой задаче.

Если нужен отдельный вызов метода, сначала проверьте значение. Сохраните тип EventCard из предыдущего примера и добавьте функцию:

main.ts
function getShortDescription(event: EventCard): string {
  if (event.description === undefined) {
    return "Описание появится позже";
  }
 
  return event.description.trim();
}

Поиск и индекс массива

find не обещает найти элемент. Сохраните этот самостоятельный пример:

main.ts
const events = [
  { id: 1, name: "Кинопоказ" },
  { id: 2, name: "Лекция" },
];
 
const selected = events.find((event) => event.id === 3);
 
if (selected === undefined) {
  console.log("Мероприятие не найдено");
} else {
  console.log(selected.name);
}

У selected тип объекта или undefined. При включённом noUncheckedIndexedAccess возможность отсутствия учитывается и в записи events[0]: массив может оказаться пустым. Проверяйте полученное значение, прежде чем читать его свойства.

Попробуйте: замените в поиске 3 на 1. Затем верните 3 и попробуйте обратиться к selected.name перед проверкой. Редактор укажет на возможность отсутствия объекта.

Элемент страницы может не найтись

Этот пример относится к браузеру, где есть document; в обычном запуске через Bun DOM нет:

main.ts
const output = document.querySelector("#result");
 
if (output !== null) {
  output.textContent = "Мероприятия загружены";
}

querySelector возвращает элемент или null. Проверка защищает случай, когда HTML изменился или в селекторе ошибка.

Если нужно свойство конкретного вида элемента, проверьте и его:

main.ts
const field = document.querySelector("#event-name");
 
if (field instanceof HTMLInputElement) {
  console.log(field.value);
}

instanceof HTMLInputElement одновременно проверяет наличие элемента и то, что это поле ввода. Запись field as HTMLInputElement лишь убедила бы TypeScript поверить разработчику: она не исправляет селектор и не проверяет DOM. По той же причине ! после значения не заменяет проверку отсутствия.

Успех и ошибка с разными данными

Для результата бронирования нужны разные свойства: при успехе – номер билета, при отказе – причина. Опишем два целых варианта:

main.ts
type BookingResult =
  { ok: true; ticketCode: string } | { ok: false; reason: string };
 
function showBooking(result: BookingResult): string {
  if (result.ok) {
    return `Ваш билет: ${result.ticketCode}`;
  }
 
  return `Запись не выполнена: ${result.reason}`;
}
 
console.log(showBooking({ ok: true, ticketCode: "T-12" }));
console.log(showBooking({ ok: false, reason: "Нет свободных мест" }));

Поле ok позволяет отличить один вариант от другого. В успешной ветке доступен ticketCode, в неуспешной – reason. Такую модель называют размеченным объединением. Она не позволяет создать «успешный результат без билета», если мы соблюдаем типы.

Это понятнее, чем объект с тремя необязательными полями: иначе пришлось бы отдельно выяснять, какие из них должны присутствовать вместе.

Неизвестные данные: unknown вместо any

Данные из внешнего источника могут оказаться чем угодно. unknown честно отражает это и требует проверку перед использованием:

main.ts
function readName(value: unknown): string {
  if (typeof value !== "string") {
    return "Имя не указано";
  }
 
  const name = value.trim();
  return name === "" ? "Имя не указано" : name;
}
 
console.log(readName("  Анна  ")); // "Анна"
console.log(readName(42)); // "Имя не указано"

С any редактор разрешил бы вызвать trim даже без проверки. Для числа это закончилось бы ошибкой во время выполнения. Не заменяйте непонятное сообщение TypeScript на any или as: сначала выясните, какие данные возможны и какая проверка нужна.

То же касается catch: при строгих настройках пойманная ошибка имеет тип unknown. Для её сообщения можно проверить error instanceof Error, а для другого значения показать запасной текст.

Далее разберём, как использовать существующие модели в новых задачах. Документация: сужение типов, strictNullChecks, noUncheckedIndexedAccess.