Kodokon kodokon.com

Литеральные типы и размеченные объединения

Описывай состояния своего приложения объединениями, которые компилятор проверяет за тебя.

8 мин · 3 вопросов

Открыть этот урок в Kodokon

Литеральный тип ограничивает значение точным содержимым: 'info', а не string. Это основа API, которые невозможно использовать неправильно: компилятор отклонит любое значение вне списка, а редактор предложит автодополнение.

TYPESCRIPT
type Level = "info" | "warn" | "error";

function log(level: Level, message: string): void {
  console.log(`[${level}] ${message}`);
}

log("info", "Server started");
// log("fatal", "Boom"); -> rejected by the compiler
Объединение литералов жёстко фиксирует допустимые значения

Следующий шаг - объединить объектные типы, у которых есть общее литеральное свойство, называемое дискриминантом. Каждое состояние сетевого запроса, например, несёт только те данные, которые ему принадлежат: обратиться к data во время загрузки просто невозможно.

TYPESCRIPT
type ApiState =
  | { status: "loading" }
  | { status: "success"; data: string[] }
  | { status: "error"; message: string };

function render(state: ApiState): string {
  switch (state.status) {
    case "loading":
      return "Loading...";
    case "success":
      return state.data.join(", ");
    case "error":
      return state.message;
  }
}
Объединение, размеченное свойством status

В каждом case TypeScript сужает тип: после case 'error' свойство state.message существует, а state.data - нет. Этот механизм, называемый сужением типа, заменяет хрупкие ручные проверки. Добавь охранник полноты, чтобы закрепить всю конструкцию.

TYPESCRIPT
function assertNever(value: never): never {
  throw new Error("Unhandled case: " + String(value));
}

function label(state: ApiState): string {
  switch (state.status) {
    case "loading": return "In progress";
    case "success": return "Done";
    case "error": return "Failed";
    default: return assertNever(state);
  }
}
Охранник полноты через тип never

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Какова роль общего свойства вроде status в размеченном объединении?
    • Повышать производительность во время выполнения
    • Позволять компилятору сужать тип в каждой ветке
    • Делать свойство обязательным в приходящем JSON
    • Запрещать дублирование свойств
  2. С типом type Level = 'info' | 'warn' какой вызов log(level, msg) скомпилируется?
    • log('debug', msg)
    • log('info', msg)
    • log(someString, msg), где someString имеет тип string
  3. Зачем нужна функция assertNever(value: never) в ветке default конструкции switch?
    • Чтобы логировать ошибки в продакшене
    • Чтобы забытый case превращался в ошибку компиляции
    • Чтобы привести значение к типу never
    • Чтобы программа завершалась быстрее