Перейти к основному содержимому

Операторы и условные ветвления в TypeScript

Разработчику

Дальше: Циклы · Асинхронность · Типы и типизация · Справочник — объединения и пересечения


Операторы if, switch, ===, &&, ?? в TypeScript совпадают с JavaScript, но ветки часто сужают типnarrowing (сужение типа): компилятор запоминает результат проверки и разрешает доступ только к полям подходящей ветки union. Это связывает условия с системой типов и убирает целый класс ошибок доступа к полям. Статья логично следует за переменными; дальше — циклы и асинхронность с LoadState.

База JS: условия и циклы. Discriminated unions — Типы данных и типизация в TypeScript.


Сравнение и логика

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…

const a = 5;
const b = "5";

console.log(a == b); // true — приведение типов
console.log(a === b); // false — предпочтительно в TS/JS
ОператорНазначение
=== / !==строгое сравнение без приведения
&&, ||короткое замыкание; || осторожно с 0 и ""
??значение по умолчанию только для null / undefined
?.optional chaining
const port = config.port ?? 3000;
const label = input || "по умолчанию"; // 0 и "" подставят значение по умолчанию

Разбор:

  • Для дефолтов конфигурации в strict-коде чаще ??, реже |||| ложными считаются 0 и "").
  • ?. возвращает undefined, если левая часть null/undefined — тип результата учитывает это.

if и сужение типов (narrowing)

typeof для примитивов

function format(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}

Разбор:

  • В ветке string компилятор знает тип value.
  • После if в else остаётся number.

Проверка на null / undefined

function len(text: string | null): number {
if (text === null) {
return 0;
}
return text.length;
}

Truthiness — осторожно

function printCount(n: number | undefined): void {
if (n) {
// здесь n: number, но 0 отфильтрован!
console.log(n);
}
}

Разбор:

  • if (n) отсекает 0 — для чисел лучше n !== undefined && n !== null или n != null.

Оператор in для объектов

type Cat = { kind: "cat"; meow: () => void };
type Dog = { kind: "dog"; bark: () => void };
type Pet = Cat | Dog;

function speak(pet: Pet): void {
if ("meow" in pet) {
pet.meow();
} else {
pet.bark();
}
}

Разбор:

  • in проверяет наличие свойства в объекте и сужает union.
  • Хорошо сочетается с полем-дискриминантом kind, но не заменяет его всегда.

Discriminated union (дискриминируемое объединение)

Discriminated union (дискриминируемое объединение) — union вариантов с общим полем-дискриминантом (kind, type, status). По значению этого поля TypeScript сужает тип в switch / if и разрешает доступ только к полям соответствующей ветки.

Теория и пример с ShapeТипы §discriminated union. Ниже — практика с switch и exhaustive check.

switch по дискриминанту

Код ITЗагрузка примера кода…

Разбор:

  • Поле statusдискриминант: в каждой ветке case доступны только релевантные поля.
  • default с neverexhaustiveness check: при добавлении варианта union компилятор укажет на неполный switch.

Тот же паттерн для UI — Типы данных и типизация в TypeScript, Асинхронное программирование в TypeScript.


Boolean-флаги и union состояний

Иногда состояние моделируют несколькими boolean (isLoading, isError, hasData). TypeScript не запрещает невозможные комбинации: isLoading: true и isError: true одновременно — типы не противоречат.

// Антипаттерн: возможны "мёртвые" состояния
type LoadFlags = {
isLoading: boolean;
isError: boolean;
data?: User;
};

function renderFlags(s: LoadFlags): string {
if (s.isLoading) return "Загрузка…";
if (s.isError) return "Ошибка";
return s.data?.name ?? "—"; // data может отсутствовать при isLoading=false
}

Discriminated union кодирует взаимоисключающие состояния одним полем:

type LoadState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: User }
| { status: "error"; message: string };

function renderState(s: LoadState): string {
switch (s.status) {
case "idle":
return "Нажмите для загрузки";
case "loading":
return "Загрузка…";
case "success":
return s.data.name; // data гарантированно есть
case "error":
return s.message;
}
}
ПодходПлюсыМинусы
Несколько booleanпривычно из JSневозможные комбинации, optional-поля без гарантий
Union с дискриминантомисчерпывающий switch, точные полячуть больше кода при описании типа
Один status: stringпростоширокий тип, нет сужения

Полный пример загрузки API — Асинхронность §LoadState.


Утверждения типов (type assertions)

Утверждение типа — вы сообщаете компилятору: "считай это значение типом T". Проверка слабее, чем при аннотации с совместимым литералом: компилятор верит вам, runtime — нет.

ФормаПримерКогда уместно
asvalue as stringпосле ручной проверки; DOM-элементы
Угловые скобки<string>valueто же; не в .tsx (конфликт с JSX)
Non-null !el!.focus()вы только что проверили на null
as const"ok" as constсужение литерала, без обхода проверки
satisfiesobj satisfies Tпроверка формы без расширения вывода
const raw: unknown = JSON.parse('{"id":"1"}');

// Плохо без проверки
// const user = raw as User;

function isUser(v: unknown): v is User {
return typeof v === "object" && v !== null && "id" in v;
}

if (isUser(raw)) {
console.log(raw.id); // сужение — предпочтительно
}

// Допустимо после проверки структуры
const el = document.getElementById("app");
if (el) {
(el as HTMLDivElement).style.display = "block";
}

Аннотация (const x: string = "a") проверяет совместимость при присваивании. Утверждение (x as string) обходит проверку. Для внешних данных порядок: unknown → guard / Zod → узкий тип — Рекомендации.

Подробнее про ! — раздел Оператор ! (non-null assertion) ниже. Сравнение с аннотацией при объявлении — Переменные §аннотация и утверждение.


if и switch

Критерийif / else ifswitch
2–3 веткиудобноизбыточно
Много вариантов unionдлинночитаемо
Exhaustive checkвручнуюdefault: never
Диапазоны чиселудобновозможно, но реже

Тернарный оператор

const badge = user.role === "admin" ? "Админ" : "Пользователь";

const message =
state.status === "error" ? state.message : "OK";

Разбор:

  • Тернарник выражение — можно присваивать и возвращать.
  • Вложенные тернарники быстро теряют читаемость — переходите на switch или функцию.

Сужение с Array.isArray и пользовательскими guard

function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every((x) => typeof x === "string");
}

function process(raw: unknown): string[] {
if (!isStringArray(raw)) {
throw new Error("Expected string[]");
}
return raw.map((s) => s.trim());
}

Разбор:


Пример parseInput

function parseInput(raw: unknown): number | null {
if (typeof raw === "number" && Number.isFinite(raw)) {
return raw;
}
if (typeof raw === "string" && raw.trim() !== "") {
const n = Number(raw);
return Number.isFinite(n) ? n : null;
}
return null;
}

Разбор:


Оператор ! (non-null assertion)

function demo(element: HTMLElement | null): void {
element!.focus(); // разработчик уверен, что элемент есть
}

Разбор:

  • ! отключает проверку на null/undefined — ошибка возможна в runtime.
  • Используйте редко; лучше if (element) element.focus().

Ветвления и тип never

Код ITЗагрузка примера кода…

Разбор:

  • assertNever в default ломает сборку, если забыли ветку — удобно в библиотечном коде.

Частые ошибки

ОшибкаПричинаЧто делать
Поле union недоступнонет сужения типа (narrowing)switch по дискриминанту
switch без break в JS-стилеfall-through редко нуженreturn в ветках или break
|| для дефолта числа0 ложный??
as вместо проверкинебезопасноtypeof, predicate
Забыли default: neverновый вариант union тихо проходитexhaustive check
== вместо ===неявное приведениестрогое сравнение

Практика

  1. Напишите parseInput(raw: unknown): number | null с typeof и проверкой Number.isFinite.
  2. Опишите union Notification с полем type и handle(n) с switch + never.
  3. Перепишите цепочку if/else на switch по status из Асинхронное программирование в TypeScript.
  4. Добавьте type predicate isUser и функцию, принимающую unknown.
  5. Найдите в проекте || для дефолта и замените на ?? там, где допустимы 0 и "".

Смежные статьи