Как работают объединения (union) и пересечения (intersection) типов?
В TypeScript, помимо базовых и пользовательских типов, существует мощный механизм — объединения (union) и пересечения (intersection). Эти конструкции позволяют описывать более гибкие модели данных и накладывать разные правила на значения. Они активно применяются для работы с динамическими структурами, API-ответами и при проектировании типобезопасных приложений.
Объединения типов (Union)
Объединение означает, что переменная или параметр функции может принимать значения нескольких типов. В коде это выражается через оператор |.
Пример:
let id: string | number;
id = "123"; // корректно
id = 123; // корректно
id = true; // ошибка
Особенности работы объединений:
-
Значение должно соответствовать хотя бы одному из типов в объединении.
-
Компилятор отслеживает доступные методы и свойства только тех типов, которые гарантированно есть у всех участников объединения.
-
Чтобы использовать особенности конкретного типа, применяется сужение типов (type narrowing) через проверки typeof, instanceof или пользовательские guards.
Пример сужения:
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed());
}
}
Таким образом, объединения позволяют описывать ситуации, где возможны несколько вариантов данных.
Пересечения типов (Intersection)
Пересечение означает, что тип должен одновременно удовлетворять всем участвующим типам. В коде это выражается через оператор &.
Пример:
type Person = {
name: string;
};
type Employee = {
employeeId: number;
};
type StaffMember = Person & Employee;
const worker: StaffMember = {
name: "Alex",
employeeId: 42
};
Особенности пересечений:
-
Объединяются свойства всех типов, и объект обязан содержать их все.
-
Если свойства совпадают, компилятор пытается их совместить. При несовместимых типах возникает ошибка.
-
Пересечения удобны при создании сложных объектов, которые должны обладать характеристиками нескольких сущностей.
Отличие объединений от пересечений
Хотя оба механизма комбинируют типы, их логика противоположна:
-
Объединение (|) — "или": переменная должна соответствовать хотя бы одному типу.
-
Пересечение (&) — "и": переменная должна соответствовать всем типам одновременно.
Пример наглядного различия:
type A = { a: number };
type B = { b: string };
type UnionAB = A | B;
type IntersectionAB = A & B;
let u: UnionAB = { a: 10 }; // корректно
u = { b: "test" }; // корректно
let i: IntersectionAB = { a: 10, b: "test" }; // корректно
Практическое применение
Объединения применяются, когда данные могут приходить в разных форматах, например, идентификатор может быть числом или строкой. Также они полезны для описания различных состояний системы, например:
type Status = "loading" | "success" | "error";
Пересечения удобны для объединения контрактов, когда объект должен реализовать сразу несколько интерфейсов или ролей. Например, пользователь может быть одновременно и администратором, и владельцем подписки:
type Admin = { isAdmin: true };
type Subscriber = { subscription: string };
type AdminSubscriber = Admin & Subscriber;
Таким образом, union помогает описывать гибкость возможных вариантов, а intersection формирует строгие требования к объекту или значению.