🟣Типы в TypeScript могут как пересекаться, так и объединяться.
Объединение множеств выполняется через вертикальную черту "|".
type StringOrNumber = string | number
type StringOrBoolean = string | boolean
const x1: StringOrNumber = 'hello'
const x2: StringOrNumber = 5
const y1: StringOrBoolean = false
const y2: StringOrBoolean = 'foo'
type Union = StringOrNumber | StringOrBoolean // string | number | boolean
type Intersected = StringOrNumber & StringOrBoolean // string
Тип StringOrNumber включает в себя все множество строк и все множество чисел. То есть мы можем создать как переменную числового значения, так и строкового.
Тип StringOrBoolean, включает в себя все множество строк и два буленовых значения false и true.
Если объединить эти два типа через |, мы получим ещё более широкий тип, который будет включать в себя все множество строк, чисел и буленовых значений.
Пересечение достигается через знак амперсанд "&"
Если мы попробуем пересечь эти два множества, то получим на выходе только string, потому что это единственное множество, которое входит в оба типа.
В TypeScript можно использовать условные типы, где тип вычисляется по условию - это похоже на тернарный оператор, но для типов.
type Union = StringOrNumber | StringOrBoolean // string | number | boolean
type Intersected = StringOrNumber & StringOrBoolean // string
type R1 = number extends Union ? 'yes' : 'no' // "yes"
type R2 = number extends Intersected ? 'yes' : 'no' // "no"
Например, мы можем проверить для прошлого примера, расширяет ли number тип Union - результат будет положительный, потому что number является подмножеством множества строк-чисел-и-буленовых значений.
А вот Intersected уже не расширяет, поэтому в итоговый тип попадет строка 'no'
🟣Нюансы объектных типов
Когда мы объединяем объектные типы, мы накладываем условие, что объект должен соответствовать хотя бы одному из указанных типов.
// Пример: состояние кнопки
type ButtonState =
| { type: 'loading'; spinner: boolean }
| { type: 'error'; message: string }
| { type: 'default'; label: string };
const btn1: ButtonState = { type: 'loading', spinner: true }; // ок
const btn2: ButtonState = { type: 'error', message: 'Oops!' }; // ок
const btn3: ButtonState = { type: 'default', label: 'Click me' }; // ок
const btn4: ButtonState = { type: 'loading', message: 'Oops!' }; // ❌ TS ругается, потому что объект должен соответствовать *только одному из вариантов*
Например, у нас есть тип ButtonState, где в зависимости от поля type может быть одно из полей: spinner, message или label.
🟣Если пересечение примитивов обычно бесполезно (так как тип не может одновременно быть и строкой и числом), то пересечение объектов (&) работает иначе: мы объединяем все свойства вместе.
// Пример: пользователь с настройками
type User = { id: number; name: string };
type Settings = { darkMode: boolean; notifications: [] };
type UserWithSettings = User & Settings;
const user1: UserWithSettings = {
id: 1,
name: 'Alice',
darkMode: true,
notifications: [],
}; // ✔️ ок
const user2: UserWithSettings = {
id: 2,
name: 'Bob',
}; // ❌ нет свойств из Settings (darkMode и notifications)
В данном примере есть два объекта:
- User с полями id и name
- Settings с полями darkMode и notifications
При пересечении этих типов (User & Settings) мы получаем новый тип, который содержит все свойства сразу: id, name, darkMode и notifications.
✈️ Telegram | 🎓 Менторство | 📹 YouTube | 👩💻 Roadmap