Enum: перечисления
Числовые, строковые и const enum — как создавать именованные наборы значений, которые делают код понятнее и безопаснее
1. Что такое перечисление — аналогия с пультом TV
Представьте пульт от телевизора. На нём есть кнопки: Power, Volume+, Volume-, Channel+, Channel-, Mute. Каждая кнопка имеет своё название и свою функцию. Вы не можете нажать кнопку, которой нет на пульте — пульт задаёт вам набор допустимых действий.
Перечисление (enum) в TypeScript — это точно такая же штука. Вы объявляете набор именованных значений, и далее в коде можете использовать только эти значения. Это называется исчерпывающим набором (exhaustive set) — ни больше, ни меньше.
Без enum вам пришлось бы работать со «голыми» строками или числами:
let direction = "north"; // Опечатка? "nort" случайно? TypeScript не знает
let role = "admin"; // А если кто-то напишет "admim"?
let status = 1; // Что значит 1? Загадка...
Проблема очевидна — ничего не мешает допустить ошибку, и TypeScript не сможет её поймать. А вот с enum:
enum Direction {
Up,
Down,
Left,
Right
}
let dir: Direction = Direction.Up; // Безопасно и понятно
Теперь переменная dir может принимать только одно из четырёх значений: Direction.Up, Direction.Down, Direction.Left или Direction.Right. Если вы попытаетесь присвоить что-то другое — TypeScript покажет ошибку.
Ключевые преимущества enum:
✅ Читаемость — вместо «магических чисел» используются осмысленные имена
✅ Безопасность — нельзя присвоить значение, которого нет в перечислении
✅ Автодополнение — IDE подсказывает все доступные варианты
✅ Рефакторинг — переименовали значение — обновилось везде
✅ Набор значений — можно перебрать все варианты
2. Числовые enum
Самый распространённый вид перечислений. Если вы не указываете значения явно, TypeScript начинает нумеровать элементы с нуля.
Простой числовой enum
enum Season {
Spring, // 0
Summer, // 1
Autumn, // 2
Winter // 3
}
console.log(Season.Spring); // 0
console.log(Season.Summer); // 1
console.log(Season.Autumn); // 2
console.log(Season.Winter); // 3
Обратите внимание: запятые после каждого значения — это стандарт. Запятая после последнего элемента (Winter) не обязательна, но и не запрещена.
Числовой enum с явными значениями
Вы можете задать конкретные числа вместо автоматической нумерации:
enum HttpStatus {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
ServerError = 500
}
console.log(HttpStatus.OK); // 200
console.log(HttpStatus.NotFound); // 404
console.log(HttpStatus.ServerError); // 500
Теперь вместо магического числа 404 в коде будет HttpStatus.NotFound — сразу понятно, что это ошибка «не найдено».
Смешанное нумерование
Можно задать значение первому элементу, а остальные пойдут по порядку:
enum Status {
Pending = 1, // Явно задали 1
Active, // 2 (автоматически +1)
Completed, // 3
Cancelled // 4
}
console.log(Status.Pending); // 1
console.log(Status.Active); // 2
console.log(Status.Completed); // 3
console.log(Status.Cancelled); // 4
Совет: Когда вы задаёте значение первому элементу, TypeScript продолжает нумерацию от этого значения. Если задать Pending = 5, то следующие будут 6, 7, 8 и так далее.
Пример: роли пользователей
enum UserRole {
Guest, // 0
User, // 1
Moderator, // 2
Admin, // 3
SuperAdmin // 4
}
let currentRole: UserRole = UserRole.User;
console.log(currentRole); // 1
Код стал читаемым. Вместо let currentRole = 1; мы видим UserRole.User — и сразу понимаем смысл.
3. Строковые enum
Строковые enum — это перечисления, где каждому элементу соответствует строковое значение вместо числа. Это удобно, когда:
- Вы отправляете данные на сервер и хотите видеть осмысленные строки в JSON
- Вы логируете значения и хотите видеть читаемый вывод
- Вы отлаживаете код — строкилегче понять, чем числа
Простой строковый enum
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
Yellow = "YELLOW"
}
let myColor: Color = Color.Red;
console.log(myColor); // "RED"
console.log(typeof myColor); // "string"
Обратите внимание: у строковых enum нет автоматической нумерации. Каждое значение нужно задавать явно — иначе TypeScript покажет ошибку.
Ошибка при пропуске значения
// ❌ Ошибка! Строковые enum требуют явных значений
enum Color {
Red, // Ошибка: Требуется инициализатор
Green,
Blue
}
// ✅ Правильно
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
Строковый enum для API
enum OrderStatus {
Pending = "pending",
Processing = "processing",
Shipped = "shipped",
Delivered = "delivered",
Cancelled = "cancelled"
}
function getStatusMessage(status: OrderStatus): string {
switch (status) {
case OrderStatus.Pending:
return "Ваш заказ ожидает обработки";
case OrderStatus.Processing:
return "Ваш заказ обрабатывается";
case OrderStatus.Shipped:
return "Ваш заказ отправлен";
case OrderStatus.Delivered:
return "Ваш заказ доставлен";
case OrderStatus.Cancelled:
return "Ваш заказ отменён";
}
}
let currentStatus = OrderStatus.Shipped;
console.log(getStatusMessage(currentStatus));
// "Ваш заказ отправлен"
Преимущество строковых enum: когда вы отправляете данные на сервер, в JSON будет не число 2, а строка "shipped" — сразу понятно и при отладке, и при чтении логов.
Строковый enum для дней недели
enum DayOfWeek {
Monday = "Понедельник",
Tuesday = "Вторник",
Wednesday = "Среда",
Thursday = "Четверг",
Friday = "Пятница",
Saturday = "Суббота",
Sunday = "Воскресенье"
}
let today: DayOfWeek = DayOfWeek.Wednesday;
console.log(today); // "Среда"
function isWeekend(day: DayOfWeek): boolean {
return day === DayOfWeek.Saturday || day === DayOfWeek.Sunday;
}
console.log(isWeekend(DayOfWeek.Saturday)); // true
console.log(isWeekend(DayOfWeek.Monday)); // false
4. Смешанные enum
Технически вы можете комбинировать числа и строки в одном enum, но это не рекомендуется — так код становится запутанным. Однако знание о такой возможности полезно, чтобы понимать, что можно встретить в чужом коде:
// ⚠️ Не рекомендуется! Только для примера
enum MixedEnum {
First = "first",
Second = 2,
Third = "third"
}
console.log(MixedEnum.First); // "first"
console.log(MixedEnum.Second); // 2
console.log(MixedEnum.Third); // "third"
Важно: Смешанные enum создают путаницу. Всегда предпочитайте либо числовой, либо строковый enum. Если вам нужны и числа, и строки — создайте два отдельных enum или используйте объект-константу (об этом поговорим позже).
5. Const enum
const enum — это особый вид перечисления, которое полностью стирается при компиляции. TypeScript заменяет все обращения к const enum на соответствующие значения «на лету».
Обычный enum vs const enum
// Обычный enum — создаёт объект в JavaScript
enum Direction {
Up,
Down,
Left,
Right
}
// const enum — стирается при компиляции
const enum DirectionConst {
Up,
Down,
Left,
Right
}
Что происходит при компиляции
// Исходный код (TypeScript)
const enum ColorConst {
Red,
Green,
Blue
}
let c = ColorConst.Red;
// Что получается после компиляции (JavaScript):
// let c = 0; // enum полностью исчез!
Вот что TypeScript делает с обычным enum:
// Обычный enum после компиляции — объект остаётся!
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
Как видите, обычный enum создаёт целый JavaScript-объект. А const enum — нет. Это значит:
Преимущества const enum:
✅ Меньше кода в бандле — объект не создаётся
✅ Быстрее выполнение — обращение к числу быстрее, чем к свойству объекта
✅ Экономия места — нет JavaScript-кода для enum
Недостатки:
❌ Нет обратного маппинга (об этом ниже)
❌ Сложнее отладка — в консоли видно только число
❌ Проблемы с some библиотеками, которые ожидают обычный enum
Пример: битовые флаги
const enum особенно полезен для битовых флагов, где экономия места важна:
const enum Permission {
Read = 1, // 0001
Write = 2, // 0010
Execute = 4, // 0100
Admin = 8 // 1000
}
let userPermission = Permission.Read | Permission.Write;
// userPermission = 1 | 2 = 3
function hasPermission(
userPerms: number,
permission: Permission
): boolean {
return (userPerms & permission) === permission;
}
console.log(hasPermission(userPermission, Permission.Read)); // true
console.log(hasPermission(userPermission, Permission.Execute)); // false
console.log(hasPermission(userPermission, Permission.Admin)); // false
Когда использовать const enum
Используйте const enum:
✅ Когда вам не нужен объект enum в runtime
✅ Когда критичен размер бандла (мобильные приложения, embedded)
✅ Когда не нужен reverse mapping
✅ Для битовых флагов и констант
Используйте обычный enum:
✅ Когда нужен reverse mapping
✅ Когда enum передаётся как значение во внешние библиотеки
✅ Когда нужно отлаживать и видеть осмысленные имена
6. Автоматические vs явные значения
Давайте разберём подробнее, как TypeScript присваивает значения элементам enum.
Автоматическая нумерация
enum Numbers {
One, // 0 (начинаем с нуля)
Two, // 1
Three, // 2
Four, // 3
Five // 4
}
// Простое правило: каждый следующий элемент = предыдущий + 1
Явные значения — полный контроль
enum Priority {
Low = 1, // Явно задали 1
Medium = 5, // Явно задали 5
High = 10, // Явно задали 10
Critical = 100 // Явно задали 100
}
// Значения не связаны между собой — каждый задан вручную
Комбинация: первый элемент задан, остальные автоматические
enum ErrorCode {
NotFound = 4000, // Явно: 4000
BadRequest, // Авто: 4001
Unauthorized, // Авто: 4002
Forbidden, // Авто: 4003
ServerError = 5000, // Явно: 5000
Timeout, // Авто: 5001
Internal // Авто: 5002
}
console.log(ErrorCode.NotFound); // 4000
console.log(ErrorCode.Unauthorized); // 4002
console.log(ErrorCode.ServerError); // 5000
console.log(ErrorCode.Internal); // 5002
Как только вы задали новое явное значение, счётчик сбрасывается и продолжает от него.
Опасные повторения значений
// ⚠️ TypeScript РАЗРЕШАЕТ это, но это ОПАСНО
enum BadEnum {
A = 1,
B = 2,
C = 1 // Тот же номер, что и A!
}
// A и C оба равны 1 — это источник багов
console.log(BadEnum.A === BadEnum.C); // true 😱
Осторожно! TypeScript не запрещает дублирования числовых значений в enum. Это может привести к серьёзным багам. Всегда проверяйте, что ваши числовые значения уникальны. Для строковых enumэтот проблема не существует — там каждое значение уникально по определению.
7. Доступ к значениям enum
Давайте разберём все способы получить значения из enum.
Прямой доступ по имени
enum TrafficLight {
Red,
Yellow,
Green
}
// Самый распространённый способ
let currentLight = TrafficLight.Red;
console.log(currentLight); // 0
// Через точку — TypeScript покажет автодополнение!
let nextLight = TrafficLight.Yellow;
console.log(nextLight); // 1
Доступ через квадратные скобки
enum Fruit {
Apple = "apple",
Banana = "banana",
Cherry = "cherry"
}
// Доступ через точку (обычный способ)
let fruit1 = Fruit.Apple;
// Доступ через квадратные скобки
let key = "Banana";
let fruit2 = Fruit[key]; // "banana"
// Оба варианта дают одно и то же
console.log(fruit1); // "apple"
console.log(fruit2); // "banana"
Совет: Доступ через квадратные скобки полезен, когда имя элемента хранится в переменной. Например: Fruit[fruitName], где fruitName — строка, полученная от пользователя.
Получение всех значений enum
enum Role {
Guest = "guest",
User = "user",
Admin = "admin"
}
// Получаем все строковые значения
let roleValues = Object.values(Role);
console.log(roleValues); // ["guest", "user", "admin"]
// Получаем все имена
let roleNames = Object.keys(Role);
console.log(roleNames); // ["Guest", "User", "Admin"]
// Получаем все пары "имя" -> "значение"
let roleEntries = Object.entries(Role);
console.log(roleEntries);
// [["Guest", "guest"], ["User", "user"], ["Admin", "admin"]]
Проверка вхождения значения в enum
enum Season {
Spring,
Summer,
Autumn,
Winter
}
function isValidSeason(value: unknown): value is Season {
return Object.values(Season).includes(value as Season);
}
// Проверка
let input: unknown = 1;
if (isValidSeason(input)) {
// Здесь input имеет тип Season
console.log("Это сезон:", Season[input]); // "Это сезон: Summer"
} else {
console.log("Неверное значение");
}
8. Reverse mapping
Reverse mapping — это возможность получить имя элемента по его числовому значению. Это работает только с числовыми enum!
Как работает reverse mapping
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// Прямой маппинг: имя -> число
console.log(Direction.Up); // 0
console.log(Direction.Down); // 1
// Обратный маппинг: число -> имя (строка)
console.log(Direction[0]); // "Up"
console.log(Direction[1]); // "Down"
console.log(Direction[2]); // "Left"
console.log(Direction[3]); // "Right"
Практический пример: конвертация
enum HttpStatus {
OK = 200,
NotFound = 404,
ServerError = 500
}
// Преобразование числа в читаемое имя
function getStatusName(code: number): string {
return HttpStatus[code] || "Unknown";
}
console.log(getStatusName(200)); // "OK"
console.log(getStatusName(404)); // "NotFound"
console.log(getStatusName(500)); // "ServerError"
console.log(getStatusName(999)); // "Unknown"
Пример: логирование
enum LogLevel {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3
}
function log(level: LogLevel, message: string): void {
const levelName = LogLevel[level];
console.log(`[${levelName}] ${message}`);
}
log(LogLevel.Debug, "Отладочное сообщение");
// [Debug] Отладочное сообщение
log(LogLevel.Error, "Критическая ошибка!");
// [Error] Критическая ошибка!
Почему reverse mapping не работает со строковыми enum
enum StringEnum {
A = "alpha",
B = "beta"
}
// Прямой маппинг работает
console.log(StringEnum.A); // "alpha"
// ❌ Обратный маппинг НЕ работает!
console.log(StringEnum["alpha"]); // undefined 😱
Важно: Reverse mapping работает только для числовых enum. Строковые enum не имеют обратного маппинга, потому что JavaScript-объект создается так, что свойства хранятся только в одну сторону (имя → значение).
Реальный пример: маппинг HTTP-методов
enum HttpMethod {
GET = 0,
POST = 1,
PUT = 2,
DELETE = 3,
PATCH = 4
}
// Функция для отправки запроса
function sendRequest(method: HttpMethod, url: string): void {
const methodName = HttpMethod[method]; // Reverse mapping!
console.log(`Отправляем ${methodName} запрос на ${url}`);
}
sendRequest(HttpMethod.GET, "/api/users");
// Отправляем GET запрос на /api/users
sendRequest(HttpMethod.POST, "/api/users");
// Отправляем POST запрос на /api/users
sendRequest(HttpMethod.DELETE, "/api/users/123");
// Отправляем DELETE запрос на /api/users/123
9. Использование enum в функциях
Enum идеально подходят для параметров функций, когда набор допустимых значений ограничен.
Функция с enum-параметром
enum Animal {
Dog = "dog",
Cat = "cat",
Bird = "bird",
Fish = "fish"
}
function makeSound(animal: Animal): string {
switch (animal) {
case Animal.Dog:
return "Гав!";
case Animal.Cat:
return "Мяу!";
case Animal.Bird:
return "Чик-чирик!";
case Animal.Fish:
return "...";
}
}
console.log(makeSound(Animal.Dog)); // "Гав!"
console.log(makeSound(Animal.Cat)); // "Мяу!"
console.log(makeSound(Animal.Bird)); // "Чик-чирик!"
Enum как возвращаемый тип
enum Temperature {
Freezing = "freezing",
Cold = "cold",
Warm = "warm",
Hot = "hot"
}
function getTemperatureCategory(celsius: number): Temperature {
if (celsius <= 0) return Temperature.Freezing;
if (celsius <= 15) return Temperature.Cold;
if (celsius <= 30) return Temperature.Warm;
return Temperature.Hot;
}
let temp = getTemperatureCategory(25);
console.log(temp); // "warm"
Enum по умолчанию (default value)
enum Theme {
Light = "light",
Dark = "dark",
Auto = "auto"
}
function setTheme(theme: Theme = Theme.Auto): void {
console.log("Тема установлена:", theme);
}
setTheme(); // Тема установлена: auto
setTheme(Theme.Light); // Тема установлена: light
setTheme(Theme.Dark); // Тема установлена: dark
Пример: система уведомлений
enum NotificationType {
Info = "info",
Success = "success",
Warning = "warning",
Error = "error"
}
interface Notification {
type: NotificationType;
title: string;
message: string;
timestamp: Date;
}
function createNotification(
type: NotificationType,
title: string,
message: string
): Notification {
return {
type,
title,
message,
timestamp: new Date()
};
}
function displayNotification(notification: Notification): void {
const emoji =
notification.type === NotificationType.Success ? "✅" :
notification.type === NotificationType.Error ? "❌" :
notification.type === NotificationType.Warning ? "⚠️" :
"ℹ️";
console.log(`${emoji} [${notification.type.toUpperCase()}] ${notification.title}`);
console.log(` ${notification.message}`);
}
let notif = createNotification(
NotificationType.Success,
"Операция выполнена",
"Ваши данные успешно сохранены"
);
displayNotification(notif);
// ✅ [SUCCESS] Операция выполнена
// Ваши данные успешно сохранены
Пример: фильтрация данных
enum ProductCategory {
Electronics = "electronics",
Clothing = "clothing",
Food = "food",
Books = "books"
}
interface Product {
name: string;
category: ProductCategory;
price: number;
}
let products: Product[] = [
{ name: "Телефон", category: ProductCategory.Electronics, price: 50000 },
{ name: "Рубашка", category: ProductCategory.Clothing, price: 3000 },
{ name: "Яблоки", category: ProductCategory.Food, price: 200 },
{ name: "Ноутбук", category: ProductCategory.Electronics, price: 80000 },
{ name: "Книга", category: ProductCategory.Books, price: 500 },
{ name: "Кофе", category: ProductCategory.Food, price: 400 }
];
function filterByCategory(
items: Product[],
category: ProductCategory
): Product[] {
return items.filter(item => item.category === category);
}
let electronics = filterByCategory(products, ProductCategory.Electronics);
console.log("Электроника:", electronics.map(p => p.name));
// Электроника: ["Телефон", "Ноутбук"]
let food = filterByCategory(products, ProductCategory.Food);
console.log("Еда:", food.map(p => p.name));
// Еда: ["Яблоки", "Кофе"]
10. Enum vs union типы vs const объекты
В TypeScript есть три способа создать набор именованных значений. Давайте сравним их.
Вариант 1: Enum
enum Direction {
Up,
Down,
Left,
Right
}
let dir: Direction = Direction.Up;
Вариант 2: Union тип
type Direction = "up" | "down" | "left" | "right";
let dir: Direction = "up";
Вариант 3: Const объект
const Direction = {
Up: "up",
Down: "down",
Left: "left",
Right: "right"
} as const;
type Direction = typeof Direction[keyof typeof Direction];
let dir: Direction = Direction.Up;
Сравнительная таблица
┌─────────────────────┬──────────────────┬──────────────────┬──────────────────┐
│ Критерий │ Enum │ Union тип │ Const объект │
├─────────────────────┼──────────────────┼──────────────────┼──────────────────┤
│ Автодополнение │ ✅ Да │ ✅ Да │ ✅ Да │
│ Reverse mapping │ ✅ Числовые │ ❌ Нет │ ❌ Нет │
│ Runtime значение │ ✅ Есть │ ❌ Нет │ ✅ Есть │
│ Размер бандла │ ⚠️ Большой │ ✅ Нулевой │ ✅ Маленький │
│ Именование типов │ ✅ Авто │ ✅ Явное │ ⚠️ Сложный синтакс│
│ Логирование │ ⚠️ Числа/строки │ ❌ Неудобно │ ✅ Строки │
│ Производительность │ ⚠️ Хуже │ ✅ Лучше │ ✅ Лучше │
└─────────────────────┴──────────────────┴──────────────────┴──────────────────┘
Когда что использовать
Используйте enum:
• Когда нужен reverse mapping (число → имя)
• Когда enum передаётся в код на чистом JavaScript
• Когда нужен runtime объект для перебора значений
Используйте union типы:
• Для простых строковых наборов значений
• Когда не нужен runtime объект
• Когда критичен размер бандла
Используйте const объекты:
• Когда нужен и тип, и runtime значение
• Когда не нужен reverse mapping
• Когда хотите контролировать точные строковые значения
Практический пример: все три подхода
// Подход 1: Enum
enum StatusEnum {
Active = "active",
Inactive = "inactive",
Banned = "banned"
}
// Подход 2: Union тип
type StatusUnion = "active" | "inactive" | "banned";
// Подход 3: Const объект
const StatusConst = {
Active: "active",
Inactive: "inactive",
Banned: "banned"
} as const;
type StatusConstType = typeof StatusConst[keyof typeof StatusConst];
// Все три работают одинаково для типизации:
function getUserStatusEnum(status: StatusEnum): string {
return `Статус: ${status}`;
}
function getUserStatusUnion(status: StatusUnion): string {
return `Статус: ${status}`;
}
function getUserStatusConst(status: StatusConstType): string {
return `Статус: ${status}`;
}
11. Ошибки новичков
Рассмотрим самые частые ошибки, которые допускают начинающие при работе с enum.
Ошибка 1: Попытка присвоить строку числовому enum
enum Direction {
Up,
Down,
Left,
Right
}
// ❌ Ошибка: строка не присваивается числовому enum
let dir: Direction = "Up";
// ✅ Правильно:
let dir2: Direction = Direction.Up;
let dir3: Direction = 0; // Сработает, но не рекомендуется!
Важно: Числовой enum в TypeScript — это просто число. Вы можете написать let dir: Direction = 0 и это сработает. Но так нельзя делать — теряется вся выгода enum!
Ошибка 2: Забыли ключевое слово enum
// ❌ Ошибка: забыли enum
Direction {
Up,
Down
}
// ✅ Правильно:
enum Direction {
Up,
Down
}
Ошибка 3: Двоеточие вместо равно
// ❌ Ошибка: двоеточие вместо равно
enum Color {
Red: "red", // Так нельзя!
Blue: "blue"
}
// ✅ Правильно: равно
enum Color {
Red = "red",
Blue = "blue"
}
Ошибка 4: Строковый enum без значений
// ❌ Ошибка: строковый enum требует явных значений
enum Fruit {
Apple, // Ошибка: Требуется инициализатор
Banana,
Cherry
}
// ✅ Правильно:
enum Fruit {
Apple = "apple",
Banana = "banana",
Cherry = "cherry"
}
Ошибка 5: Запятая в конце (не ошибка, но путает)
// Это допустимо, но не рекомендуется
enum Color {
Red = "red",
Blue = "blue", // Запятая после последнего элемента
}
// Лучше без запятой после последнего:
enum Color {
Red = "red",
Blue = "blue"
}
Ошибка 6: Думают, что enum — это объект
enum Status {
Active = "active",
Inactive = "inactive"
}
// ❌ Ошибка: enum не является обычным объектом
for (let key in Status) {
console.log(key); // Выведет и ключи, и значения!
}
// ✅ Правильно: используйте Object.values или Object.keys
let values = Object.values(Status);
console.log(values); // ["active", "inactive"]
Ошибка 7: Сравнение enum с неверным типом
enum Color {
Red = "red",
Blue = "blue"
}
let userColor: string = "red";
// ❌ Ошибка: нельзя сравнивать string с Color
if (userColor === Color.Red) { // Ошибка компиляции
console.log("Красный!");
}
// ✅ Правильно: проверяем вхождение в enum
if (Object.values(Color).includes(userColor as Color)) {
console.log("Это допустимый цвет!");
}
Ошибка 8: Enum в switch без exhaustiveness check
enum Shape {
Circle,
Rectangle,
Triangle
}
// ⚠️ Работает, но небезопасно — забыли Triangle!
function getArea(shape: Shape): number {
switch (shape) {
case Shape.Circle:
return 3.14;
case Shape.Rectangle:
return 4;
// Triangle забыли! Нет ошибки компиляции
}
}
// ✅ Правильно: добавьте default с проверкой
function getAreaSafe(shape: Shape): number {
switch (shape) {
case Shape.Circle:
return 3.14;
case Shape.Rectangle:
return 4;
case Shape.Triangle:
return 2;
default:
const _exhaustive: never = shape;
return _exhaustive;
}
}
Совет для новичков: Используйте never в default-ветке switch по enum. Если вы забудете обработать какое-то значение — TypeScript покажет ошибку компиляции. Это называется exhaustiveness check (проверка полноты обработки).
12. Практические примеры
Давайте рассмотрим реальные ситуации, где enum незаменимы.
Пример 1: Состояние формы
enum FormState {
Idle = "idle",
Loading = "loading",
Success = "success",
Error = "error"
}
class LoginForm {
state: FormState = FormState.Idle;
errorMessage: string = "";
async submit(username: string, password: string): Promise<void> {
this.state = FormState.Loading;
try {
const response = await fetch("/api/login", {
method: "POST",
body: JSON.stringify({ username, password })
});
if (response.ok) {
this.state = FormState.Success;
} else {
this.errorMessage = "Неверный логин или пароль";
this.state = FormState.Error;
}
} catch (e) {
this.errorMessage = "Ошибка сети";
this.state = FormState.Error;
}
}
getStatusMessage(): string {
switch (this.state) {
case FormState.Idle:
return "Введите данные для входа";
case FormState.Loading:
return "Выполняется вход...";
case FormState.Success:
return "Добро пожаловать!";
case FormState.Error:
return `Ошибка: ${this.errorMessage}`;
}
}
}
let form = new LoginForm();
console.log(form.getStatusMessage()); // "Введите данные для входа"
Пример 2: Калькулятор с операциями
enum Operation {
Add = "+",
Subtract = "-",
Multiply = "*",
Divide = "/"
}
function calculate(
a: number,
b: number,
operation: Operation
): number {
switch (operation) {
case Operation.Add:
return a + b;
case Operation.Subtract:
return a - b;
case Operation.Multiply:
return a * b;
case Operation.Divide:
if (b === 0) throw new Error("Деление на ноль!");
return a / b;
}
}
console.log(calculate(10, 5, Operation.Add)); // 15
console.log(calculate(10, 5, Operation.Subtract)); // 5
console.log(calculate(10, 5, Operation.Multiply)); // 50
console.log(calculate(10, 5, Operation.Divide)); // 2
Пример 3: Конфигурация приложения
enum Environment {
Development = "development",
Staging = "staging",
Production = "production"
}
enum LogLevel {
Debug = 0,
Info = 1,
Warn = 2,
Error = 3
}
interface AppConfig {
env: Environment;
logLevel: LogLevel;
apiUrl: string;
enableCache: boolean;
}
function createConfig(env: Environment): AppConfig {
switch (env) {
case Environment.Development:
return {
env,
logLevel: LogLevel.Debug,
apiUrl: "http://localhost:3000",
enableCache: false
};
case Environment.Staging:
return {
env,
logLevel: LogLevel.Info,
apiUrl: "https://staging.api.example.com",
enableCache: true
};
case Environment.Production:
return {
env,
logLevel: LogLevel.Error,
apiUrl: "https://api.example.com",
enableCache: true
};
}
}
let config = createConfig(Environment.Production);
console.log(config);
// { env: "production", logLevel: 3, apiUrl: "https://api.example.com", enableCache: true }
Пример 4: Игровая механика
enum WeaponType {
Sword = "sword",
Bow = "bow",
Staff = "staff",
Dagger = "dagger"
}
enum DamageType {
Physical = "physical",
Magical = "magical",
Poison = "poison"
}
interface Weapon {
name: string;
type: WeaponType;
damageType: DamageType;
baseDamage: number;
criticalChance: number;
}
let weapons: Weapon[] = [
{ name: "Железный меч", type: WeaponType.Sword, damageType: DamageType.Physical, baseDamage: 15, criticalChance: 0.1 },
{ name: "Лук охотника", type: WeaponType.Bow, damageType: DamageType.Physical, baseDamage: 12, criticalChance: 0.15 },
{ name: "Посох мага", type: WeaponType.Staff, damageType: DamageType.Magical, baseDamage: 20, criticalChance: 0.05 },
{ name: "Кинжал теней", type: WeaponType.Dagger, damageType: DamageType.Physical, baseDamage: 10, criticalChance: 0.25 }
];
function calculateDamage(weapon: Weapon): number {
const isCritical = Math.random() < weapon.criticalChance;
const multiplier = isCritical ? 2 : 1;
const damage = weapon.baseDamage * multiplier;
console.log(
`${weapon.name} наносит ${damage} урона` +
(isCritical ? " (КРИТИЧЕСКИЙ УДАР!)" : "")
);
return damage;
}
calculateDamage(weapons[0]); // Железный меч наносит 15 урона
calculateDamage(weapons[2]); // Посох мага наносит 20 урона
Пример 5: Роутинг
enum Route {
Home = "/",
About = "/about",
Contact = "/contact",
Profile = "/profile",
Settings = "/settings",
Admin = "/admin"
}
function navigateTo(route: Route): void {
console.log(`Навигация на: ${route}`);
// В реальном приложении: window.history.pushState({}, "", route);
}
navigateTo(Route.Home); // Навигация на: /
navigateTo(Route.Profile); // Навигация на: /profile
navigateTo(Route.Settings); // Навигация на: /settings
Пример 6: Валидация данных
enum ValidationRule {
Required = "required",
MinLength = "minLength",
MaxLength = "maxLength",
Email = "email",
Number = "number",
Pattern = "pattern"
}
interface Validation {
rule: ValidationRule;
value?: number | string;
message: string;
}
function validate(
input: string,
validations: Validation[]
): string[] {
const errors: string[] = [];
for (const validation of validations) {
switch (validation.rule) {
case ValidationRule.Required:
if (!input.trim()) {
errors.push(validation.message);
}
break;
case ValidationRule.MinLength:
if (input.length < (validation.value as number)) {
errors.push(validation.message);
}
break;
case ValidationRule.MaxLength:
if (input.length > (validation.value as number)) {
errors.push(validation.message);
}
break;
case ValidationRule.Email:
if (!input.includes("@")) {
errors.push(validation.message);
}
break;
case ValidationRule.Number:
if (isNaN(Number(input))) {
errors.push(validation.message);
}
break;
case ValidationRule.Pattern:
const regex = new RegExp(validation.value as string);
if (!regex.test(input)) {
errors.push(validation.message);
}
break;
}
}
return errors;
}
let errors = validate("", [
{ rule: ValidationRule.Required, message: "Поле обязательно" },
{ rule: ValidationRule.MinLength, value: 3, message: "Минимум 3 символа" }
]);
console.log(errors); // ["Поле обязательно", "Минимум 3 символа"]
let emailErrors = validate("invalid-email", [
{ rule: ValidationRule.Required, message: "Поле обязательно" },
{ rule: ValidationRule.Email, message: "Неверный формат email" }
]);
console.log(emailErrors); // ["Неверный формат email"]
Пример 7: Система прав доступа
enum Permission {
Read = 1,
Write = 2,
Delete = 4,
Admin = 8
}
enum Role {
Viewer = Permission.Read,
Editor = Permission.Read | Permission.Write,
Admin = Permission.Read | Permission.Write | Permission.Delete,
SuperAdmin = Permission.Read | Permission.Write | Permission.Delete | Permission.Admin
}
function hasPermission(userRole: Role, permission: Permission): boolean {
return (userRole & permission) === permission;
}
let currentUser: Role = Role.Editor;
console.log(hasPermission(currentUser, Permission.Read)); // true
console.log(hasPermission(currentUser, Permission.Write)); // true
console.log(hasPermission(currentUser, Permission.Delete)); // false
console.log(hasPermission(currentUser, Permission.Admin)); // false
let admin: Role = Role.Admin;
console.log(hasPermission(admin, Permission.Delete)); // true
console.log(hasPermission(admin, Permission.Admin)); // false
Пример 8: State Machine
enum OrderState {
Created = "created",
Paid = "paid",
Shipped = "shipped",
Delivered = "delivered",
Cancelled = "cancelled"
}
enum OrderAction {
Pay = "pay",
Ship = "ship",
Deliver = "deliver",
Cancel = "cancel"
}
const validTransitions: Record<OrderState, OrderAction[]> = {
[OrderState.Created]: [OrderAction.Pay, OrderAction.Cancel],
[OrderState.Paid]: [OrderAction.Ship, OrderAction.Cancel],
[OrderState.Shipped]: [OrderAction.Deliver],
[OrderState.Delivered]: [],
[OrderState.Cancelled]: []
};
function canPerformAction(
currentState: OrderState,
action: OrderAction
): boolean {
return validTransitions[currentState].includes(action);
}
function performAction(
state: OrderState,
action: OrderAction
): OrderState {
if (!canPerformAction(state, action)) {
throw new Error(
`Нельзя выполнить ${action} в состоянии ${state}`
);
}
switch (action) {
case OrderAction.Pay: return OrderState.Paid;
case OrderAction.Ship: return OrderState.Shipped;
case OrderAction.Deliver: return OrderState.Delivered;
case OrderAction.Cancel: return OrderState.Cancelled;
}
}
let state: OrderState = OrderState.Created;
console.log(state); // "created"
state = performAction(state, OrderAction.Pay);
console.log(state); // "paid"
state = performAction(state, OrderAction.Ship);
console.log(state); // "shipped"
state = performAction(state, OrderAction.Deliver);
console.log(state); // "delivered"
// Ошибка! Доставленный заказ нельзя отменить
// performAction(state, OrderAction.Cancel); // Бросит ошибку
Итоги урока
Отлично! Мы подробно разобрали перечисления (enum) в TypeScript. Давайте подведём итоги.
Числовые enum:
• Автоматическая нумерация с нуля
• Можно задавать явные значения
• Поддерживают reverse mapping (число → имя)
• Создают JavaScript-объект в runtime
Строковые enum:
• Требуют явных значений для каждого элемента
• Не имеют reverse mapping
• Удобны для логирования и API
• Значения видны в консоли
Const enum:
• Полностью стирается при компиляции
• Экономит место в бандле
• Не создаёт JavaScript-объект
• Идеален для битовых флагов и констант
Ключевые моменты:
• Enum защищают от «магических чисел» и строк
• Автодополнение в IDE ускоряет разработку
• Exhaustiveness check с never гарантирует полную обработку
• Выбирайте enum vs union vs const объект в зависимости от задачи
👉 Следующий урок: Type aliases — псевдонимы типов
Научимся создавать собственные имена для типов, чтобы код был ещё чище и понятнее.
Проверяем понимание
8 вопросов