0Pricing
TypeScript Academy · Урок

Проверка полноты с помощью never

Выявляйте необработанные случаи во время компиляции с помощью never

«Проверка полноты с помощью never» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 4 уроков всего.

Проблема полноты обработки

Когда Вы добавляете новый элемент объединения, легко забыть обработать его где-нибудь ещё. Проверка полноты обработки превращает такую оплошность в ошибку компиляции.

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

// If we add "triangle" later, we want every switch to complain.

Тип never

Тип never представляет значения, которые никогда не могут возникнуть. Если обработаны все варианты, значение, попавшее в ветвь по умолчанию, имеет тип never.

function fail(): never {
  throw new Error("unreachable");
}
// never is assignable to nothing except never itself.

Присваивание never в ветви по умолчанию

В ветви default присвойте значение переменной типа never. Если обработаны все варианты, присваивание скомпилируется; если нет — произойдёт ошибка.

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "square": return s.side ** 2;
    default:
      const _exhaustive: never = s;
      return _exhaustive;
  }
}
console.log(area({ kind: "square", side: 3 }));

Что происходит, если забыть вариант

Если Вы добавите элемент triangle, но забудете его вариант, s в ветви по умолчанию больше не будет иметь тип never, поэтому присваивание завершится ошибкой во время компиляции.

// type Shape = ... | { kind: "triangle"; base: number; height: number };
// Now in default, s is { kind: "triangle"; ... }
// const _exhaustive: never = s; // Error: triangle not assignable to never

Вспомогательная функция assertNever

Повторно используемая функция assertNever выносит этот шаблон в одно место. Она принимает never и выбрасывает ошибку, показывая, что ветвь недостижима.

function assertNever(value: never): never {
  throw new Error("Unhandled case: " + JSON.stringify(value));
}
console.log(typeof assertNever);

Использование assertNever в switch

Вызовите assertNever(s) в ветви по умолчанию. Он обеспечивает полноту обработки во время компиляции и выдаёт понятную ошибку во время выполнения, если до этой ветви всё же дойдёт выполнение.

function assertNever(value: never): never {
  throw new Error("Unhandled: " + JSON.stringify(value));
}
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "square": return s.side ** 2;
    default: return assertNever(s);
  }
}
console.log(area({ kind: "circle", radius: 1 }).toFixed(2));

Безопасность во время компиляции и выполнения

Проверка с помощью never обнаруживает пропущенные варианты до запуска кода, а выброшенная ошибка защищает Вас, если что-то всё же прошло проверку типов и возникло во время выполнения.

function assertNever(x: never): never {
  throw new Error("Unhandled: " + String(x));
}
// Compile error if a case is missing; runtime throw as a backstop.
console.log("two layers of safety");

Полнота обработки без default

Если функция имеет явно заданный тип возвращаемого значения, а switch охватывает каждый вариант, TypeScript также может сообщить о пропущенном возврате — это ещё одна форма проверки полноты.

type Light = "red" | "yellow" | "green";
function next(l: Light): Light {
  switch (l) {
    case "red": return "green";
    case "yellow": return "red";
    case "green": return "yellow";
  }
  // No default needed; all cases return.
}
console.log(next("red"));

Полнота обработки в цепочках if/else

Тот же подход работает с if/else. После обработки каждого варианта последняя ветвь else получает значение типа never.

function assertNever(x: never): never { throw new Error("bad"); }
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function name(s: Shape): string {
  if (s.kind === "circle") return "circle";
  else if (s.kind === "square") return "square";
  else return assertNever(s);
}
console.log(name({ kind: "square", side: 2 }));

Почему never — правильный инструмент

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

// Only never is assignable to never.
let x: never;
// x = "hi"; // Error
// Any concrete leftover type fails the same way.
console.log("never catches gaps");

Полнота обработки как страховка при рефакторинге

Если использовать assertNever повсюду, добавление элемента объединения создаст аккуратный список ошибок компиляции, указывающих точно на каждое место, которое нужно обновить.

function assertNever(x: never): never { throw new Error("unhandled"); }
type Status = "idle" | "busy";
function render(s: Status): string {
  switch (s) {
    case "idle": return "Idle";
    case "busy": return "Busy";
    default: return assertNever(s);
  }
}
console.log(render("idle"));

Быстрая проверка: полнота обработки

Проверьте, насколько хорошо Вы поняли проверку полноты обработки.

Итоги: полнота обработки с never

Вы научились присваивать значение по умолчанию переменной типа never (или передавать его в assertNever), чтобы компилятор заставлял Вас обрабатывать каждый вариант. Благодаря этому забытые варианты превращаются в ошибки компиляции.

function assertNever(x: never): never { throw new Error("unhandled"); }
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
function f(s: Shape) {
  switch (s.kind) {
    case "circle": return s.radius;
    case "square": return s.side;
    default: return assertNever(s);
  }
}
console.log(f({ kind: "circle", radius: 5 }));

Часто задаваемые вопросы

Урок «Проверка полноты с помощью never» бесплатный?

Да — полный текст урока «Проверка полноты с помощью never» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 4 уроков всего.

Чему я научусь в уроке «Проверка полноты с помощью never»?

Выявляйте необработанные случаи во время компиляции с помощью never Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать TypeScript Academy?

Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Проверка полноты с помощью never»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке TypeScript Academy?

Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Создание дискриминируемых объединений
  2. Сужение по дискриминатору
  3. Проверка полноты с помощью never
  4. Моделирование конечных автоматов
← Назад к TypeScript Academy