0Pricing
TypeScript Academy · Урок

Сужение по дискриминатору

Позвольте TypeScript сужать варианты в операторах switch

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

Сужение по дискриминанту

После проверки дискриминанта TypeScript сужает объединение до подходящего элемента и открывает доступ к его специфическим полям. В этом и состоит главное преимущество такого подхода.

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

function area(s: Shape) {
  if (s.kind === "circle") return Math.PI * s.radius ** 2;
  return s.side ** 2;
}
console.log(area({ kind: "circle", radius: 2 }).toFixed(2));

Переключение по дискриминанту

Конструкция switch по дискриминанту — самый понятный способ обработать каждый вариант. Внутри каждого case тип сужается автоматически.

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;
  }
}
console.log(area({ kind: "square", side: 5 }));

Поля конкретного элемента в каждом варианте

В варианте "circle" доступно поле s.radius, а s.side недоступно. TypeScript точно знает, с каким элементом Вы работаете.

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

function describe(s: Shape) {
  switch (s.kind) {
    case "circle": return "r=" + s.radius;
    case "square": return "side=" + s.side;
  }
}
console.log(describe({ kind: "circle", radius: 9 }));

Сужение с помощью if

Вам не обязательно использовать switch. Простое сравнение дискриминанта в конструкции if выполняет сужение столь же эффективно.

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

function perimeter(s: Shape): number {
  if (s.kind === "circle") {
    return 2 * Math.PI * s.radius;
  }
  return 4 * s.side;
}
console.log(perimeter({ kind: "square", side: 3 }));

Обращение к неправильному полю приводит к ошибке

До сужения нельзя обращаться к полю, специфичному для отдельного элемента. TypeScript сообщает об ошибке, поскольку это свойство существует не у каждого элемента.

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

function broken(s: Shape) {
  // Error: radius does not exist on Square
  // return s.radius;
  return s.kind;
}
console.log(broken({ kind: "square", side: 1 }));

Сужение с логическими дискриминантами

Для объединения { ok: true } | { ok: false } проверка if (r.ok) сужает тип до элемента, соответствующего успешному результату.

type Result =
  | { ok: true; data: string }
  | { ok: false; error: string };

function handle(r: Result): string {
  if (r.ok) return "Data: " + r.data;
  return "Error: " + r.error;
}
console.log(handle({ ok: false, error: "boom" }));

Сужение с досрочным возвратом

Досрочный возврат для одного варианта сужает оставшуюся часть кода до других вариантов. Это удобный способ последовательно исключать варианты.

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

function label(s: Shape): string {
  if (s.kind === "circle") return "circle";
  // s is now narrowed to Square here
  return "square with side " + s.side;
}
console.log(label({ kind: "square", side: 8 }));

Комбинирование с другими условиями

Проверку дискриминанта можно сочетать с другой логикой. TypeScript сохраняет сужение, пока условие дискриминанта остаётся истинным в этой ветви.

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

function bigCircle(s: Shape): boolean {
  return s.kind === "circle" && s.radius > 100;
}
console.log(bigCircle({ kind: "circle", radius: 150 }));

Сужение в фильтрах массивов

Сужение работает и внутри обратных вызовов. Здесь каждая фигура проверяется отдельно по мере перебора.

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

const shapes: Shape[] = [
  { kind: "circle", radius: 1 },
  { kind: "square", side: 2 }
];
shapes.forEach(s => {
  if (s.kind === "circle") console.log("c", s.radius);
  else console.log("s", s.side);
});

Возврат из switch сохраняет безопасность типов

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

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

function tag(s: Shape) {
  switch (s.kind) {
    case "circle": return s.radius;
    case "square": return s.side;
  }
}
console.log(tag({ kind: "circle", radius: 4 }));

Объединяем приёмы сужения

Используете ли Вы switch или if, именно проверка дискриминанта делает обращение к полям элемента безопасным. Далее мы обеспечим обработку каждого варианта.

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

function area(s: Shape): number {
  return s.kind === "circle" ? Math.PI * s.radius ** 2 : s.side ** 2;
}
console.log(Math.round(area({ kind: "circle", radius: 10 })));

Быстрая проверка: сужение

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

Итоги: сужение по дискриминанту

Вы увидели, что проверка дискриминанта с помощью switch или if сужает объединение до одного элемента, открывая доступ только к полям этого элемента. Досрочные возвраты и составные условия также сохраняют сужение.

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

const s: Shape = { kind: "square", side: 7 };
console.log(s.kind === "square" ? s.side : 0);

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

Урок «Сужение по дискриминатору» бесплатный?

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

Чему я научусь в уроке «Сужение по дискриминатору»?

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

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

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

Сколько времени занимает урок «Сужение по дискриминатору»?

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

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

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

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

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