Union Types
Union Types (
A | B) — тип, значение которого может быть одним из нескольких указанных типов; TypeScript требует обрабатывать все варианты или сужать тип перед использованием конкретных методов.
Зачем нужно
Union types точно описывают реальное разнообразие данных: функция может возвращать User | null, параметр принимает string | number, API присылает Success | Error. Без union пришлось бы использовать any или писать несколько перегрузок.
Где используется
- Функции, принимающие несколько типов аргументов
- Nullable значения:
string | null,User | undefined - Результаты операций:
Result | Error - Discriminated unions для state machine
- Библиотечные типы:
string | numberдля ID
Основной контент
Базовый синтаксис
type StringOrNumber = string | number;
function format(value: string | number): string {
// Нельзя сразу вызвать .toFixed() — нужно сужение
if (typeof value === "string") {
return value.toUpperCase(); // value: string
}
return value.toFixed(2); // value: number
}
format("hello"); // "HELLO"
format(3.14159); // "3.14"
Nullable типы
// С strict: true, null и undefined нужно явно указывать
function getUser(id: string): User | null {
return users.find(u => u.id === id) ?? null;
}
const user = getUser("u-1");
// user.name; // Error: Object is possibly 'null'
if (user !== null) {
console.log(user.name); // OK
}
// Optional chaining + nullish coalescing
const name = getUser("u-1")?.name ?? "Guest";
Union с несколькими типами
type Id = string | number;
type Status = "pending" | "active" | "inactive";
type Value = string | number | boolean | null | undefined;
function processId(id: Id): string {
return String(id);
}
Сужение union типов
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
// typeof narrowing
function describe(val: string | number): string {
if (typeof val === "string") return `String: ${val}`;
return `Number: ${val}`;
}
// Discriminated union narrowing
function area(shape: Shape): number {
if (shape.kind === "circle") return Math.PI * shape.radius ** 2;
return shape.side ** 2;
}
// instanceof narrowing
function handleError(err: Error | string): string {
if (err instanceof Error) return err.message;
return err;
}
Union в generic context
// Из двух массивов — union элементов
function merge<A, B>(a: A, b: B): (A | B) {
return [...a, ...b];
}
const mixed = merge([1, 2], ["a", "b"]); // (number | string)
keyof и union
interface User { name: string; email: string; age: number }
type UserKey = keyof User; // "name" | "email" | "age"
function getField(user: User, key: keyof User): User[keyof User] {
return user[key];
}
Частые ошибки
- Использовать метод без сужения —
(val: string | number).toFixed()— ошибка;toFixedесть только уnumber. string | number | stringне упрощается автоматически — нужно самому убрать дубли.- Путать
|и&—|означает «одно из»,&означает «оба сразу». - Не использовать exhaustive check — если добавить вариант в union и забыть обновить switch — ошибки не будет без
assertNever.
Альтернативная позиция: запрет UNION types
"Я запрещаю UNION types в TypeScript".
Это не консенсус, но обоснованная позиция. Краткий список аргументов:
-
Union → if-ы в коде. Каждый union ведёт к рантайм-проверкам
typeof/instanceof. Это слипшиеся абстракции, которые надо было разделить.// Плохо — Запрещается type Body = ArrayBuffer | Buffer | string; function send(body: Body) { if (body instanceof ArrayBuffer) { /* ... */ } else if (body instanceof Buffer) { /* ... */ } else if (typeof body === 'string') { /* ... */ } } -
Union прячет смешение абстракций.
function pay(money: number | string | Money)— это три разных контракта, склеенных в один параметр. -
Плохой union ломает мономорфность V8 — оптимизатор не может зафиксировать форму, hidden classes расходятся, deopt.
-
Когнитивная нагрузка. Читатель должен в уме перебрать все варианты ветвления.
Допустимые union (по автору)
- ✅ Union строковых литералов:
'pending' | 'active' | 'done'— V8 видит просто string. - ✅ Union чисел/состояний: коды ошибок, HTTP-методы.
- ✅ Union типов с общим интерфейсом:
Buffer | null(оба object),MailTarget = User | Company(оба имеют email). - ✅ Overload-функции (формально union, но через сигнатуры).
- ✅ Thenable:
Promise<T> | T.
Запрещённый union (по автору)
- ❌ Несвязанные типы:
User | Socket | string. - ❌
name: string | string— лучше всегдаnames: string. - ❌
money: number | string | Money— выбери одну форму.
"Если union-type, значит где-то логика уехала в if-ы."
Связанные темы
- Intersection Types
- Discriminated Unions
- Literal Types
- Type Guards -- typeof и instanceof
- Пользовательские Type Guards
- TS vs JS -- философия Тимура
- _MOC TypeScript
Источники
- 🎓 Я запрещаю UNION types в TypeScript и ORM · 2025-12-17 · YouTube
- Тезисы: union → if-ы; union строк ок для V8; union несвязанных типов — антипаттерн.
- Позиция автора (provocative): "Я запрещаю UNION types". Допустимы только union по общему контракту/интерфейсу или union литералов.