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

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

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

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


Операторы if, switch, ===, &&, ?? в TypeScript совпадают с JavaScript, но ветки часто сужают тип (type narrowing). Это связывает условия с системой типов и убирает целый класс ошибок доступа к полям.

Маршрут: переменныеветвленияциклыАсинхронность с LoadState.

База JS: условия и циклы. Discriminated unions — 10.md.


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

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-коде чаще ??, не ||.
  • ?. возвращает 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 и switch

type ApiResult =
| { status: "ok"; data: string }
| { status: "error"; code: number };

function handle(result: ApiResult): string {
switch (result.status) {
case "ok":
return result.data;
case "error":
return `Ошибка ${result.code}`;
default: {
const _exhaustive: never = result;
return _exhaustive;
}
}
}

Разбор:

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

Тот же паттерн для UI — 10.md, 17.md.


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());
}

Разбор:

  • value is string[] — type predicate: после if тип сужен.
  • Предпочтительнее as string[] для внешних данных — 6.md.

Пример 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;
}

Разбор:

  • Возврат null явно описывают в типе — вызывающий обязан обработать.
  • Альтернатива — throw или Result27.md.

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

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

Разбор:

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

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

function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`);
}

type Shape = { kind: "circle"; r: number } | { kind: "square"; a: number };

function area(s: Shape): number {
switch (s.kind) {
case "circle":
return Math.PI * s.r ** 2;
case "square":
return s.a ** 2;
default:
return assertNever(s);
}
}

Разбор:

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

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

ОшибкаПричинаЧто делать
Поле union недоступнонет narrowingswitch по дискриминанту
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 из 17.md.
  4. Добавьте type predicate isUser и функцию, принимающую unknown.
  5. Найдите в проекте || для дефолта и замените на ?? там, где допустимы 0 и "".

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