Операторы и условные ветвления в 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сnever— exhaustiveness 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 — нет.
| Форма | Пример | Когда уместно |
|---|---|---|
as | value as string | после ручной проверки; DOM-элементы |
| Угловые скобки | <string>value | то же; не в .tsx (конфликт с JSX) |
Non-null ! | el!.focus() | вы только что проверили на null |
as const | "ok" as const | сужение литерала, без обхода проверки |
satisfies | obj 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 if | switch |
|---|---|---|
| 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[]для внешних данных — Рекомендации по разработке на TypeScript.
Пример 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илиResult— Обработка ошибок в TypeScript.
Оператор ! (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 |
== вместо === | неявное приведение | строгое сравнение |
Практика
- Напишите
parseInput(raw: unknown): number | nullсtypeofи проверкойNumber.isFinite. - Опишите union
Notificationс полемtypeиhandle(n)сswitch+never. - Перепишите цепочку
if/elseнаswitchпоstatusиз Асинхронное программирование в TypeScript. - Добавьте type predicate
isUserи функцию, принимающуюunknown. - Найдите в проекте
||для дефолта и замените на??там, где допустимы0и"".
Смежные статьи
- Типы — union,
never,LoadState - Переменные — optional chaining
- Циклы —
forс условиями - Асинхронность — union состояний загрузки
- Обработка ошибок — Result, throw
- Справочник — Справочник — объединения и пересечения
- JS: Циклы в JavaScript