Enum
Enum (перечисление) — именованный набор констант, объединённых общим смыслом: числовые, строковые и const enum.
Зачем нужно
- Группировка связанных констант под одним именем
- Автодополнение и проверка допустимых значений
- Самодокументирующийся код —
Status.Activeпонятнее чем1 - Защита от опечаток и недопустимых значений
Где используется
- Статусы:
OrderStatus.Pending,UserRole.Admin - Конфигурация:
LogLevel.Error,Direction.Up - HTTP-коды, коды ошибок, режимы работы
- API-контракты: фиксированный набор допустимых значений
Предпосылки
- Примитивные типы — number и string
- Литеральные типы — литеральные типы как альтернатива
Числовые enum
По умолчанию enum — числовой, значения автоинкрементируются от 0:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
let dir: Direction = Direction.Up;
console.log(dir); // 0
// Можно задать начальное значение
enum StatusCode {
OK = 200,
Created = 201,
BadRequest = 400,
NotFound = 404,
InternalError = 500,
}
console.log(StatusCode.OK); // 200
// Автоинкремент от заданного значения
enum Priority {
Low = 1,
Medium, // 2
High, // 3
}
Reverse Mapping (обратное отображение)
Числовые enum поддерживают обратный маппинг — можно получить имя по значению:
enum Color {
Red, // 0
Green, // 1
Blue, // 2
}
console.log(Color.Red); // 0
console.log(Color[0]); // "Red" — обратный маппинг
console.log(Color[1]); // "Green"
// Что генерируется в JS:
// var Color;
// (function (Color) {
// Color[Color["Red"] = 0] = "Red";
// Color[Color["Green"] = 1] = "Green";
// Color[Color["Blue"] = 2] = "Blue";
// })(Color || (Color = {}));
Строковые enum
Каждому члену нужно задать строковое значение:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
console.log(Direction.Up); // "UP"
// Строковые enum НЕ поддерживают reverse mapping
// Direction["UP"] — ошибка
// Полезно для API и сериализации
enum ApiEndpoint {
Users = "/api/users",
Posts = "/api/posts",
Comments = "/api/comments",
}
fetch(ApiEndpoint.Users); // fetch("/api/users")
Гетерогенные enum (смешанные)
Технически возможно, но не рекомендуется:
// НЕ рекомендуется
enum Mixed {
No = 0,
Yes = "YES",
}
Const Enum
const enum полностью удаляется при компиляции — значения инлайнятся:
const enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
const dir = Direction.Up;
// Компилируется в: const dir = "UP";
// Никакого объекта Direction в рантайме!
Преимущества const enum
const enum StatusCode {
OK = 200,
NotFound = 404,
}
if (response.status === StatusCode.OK) {
// Компилируется в: if (response.status === 200)
// Нет overhead от объекта enum
}
Ограничения const enum
const enum Color {
Red,
Green,
Blue,
}
// Нельзя использовать:
// Color[0] — нет reverse mapping
// Object.keys(Color) — объекта нет в рантайме
// const c: Color = getColor — нельзя динамически
// Проблема с isolatedModules (Babel, esbuild):
// const enum не работает с isolatedModules: true
// Решение: обычный enum или union types
Enum как тип
enum Role {
Admin = "ADMIN",
User = "USER",
Guest = "GUEST",
}
// Enum можно использовать как тип
function checkAccess(role: Role): boolean {
return role === Role.Admin;
}
checkAccess(Role.Admin); // OK
checkAccess("ADMIN"); // Ошибка! Строка "ADMIN" !== Role.Admin
// Для числовых enum — осторожно!
enum NumericRole {
Admin, // 0
User, // 1
}
function check(role: NumericRole): void {}
check(NumericRole.Admin); // OK
check(0); // OK — числа совместимы с числовым enum
check(999); // OK! Это "дырка" — любое число принимается
Альтернативы Enum — Union Types
Во многих случаях union типы предпочтительнее enum:
// Вместо enum:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
// Union type:
type Direction = "UP" | "DOWN" | "LEFT" | "RIGHT";
// Использование одинаковое:
function move(dir: Direction): void {}
move("UP"); // OK
move("DIAGONAL"); // Ошибка!
Сравнение Enum vs Union
| Критерий | Enum | Union Type |
|---|---|---|
| Runtime-объект | Да (кроме const enum) | Нет |
| Tree-shaking | Может не работать | Работает |
| isolatedModules | Проблемы с const enum | Нет проблем |
| Reverse mapping | Числовые — да | Нет |
| Итерация по значениям | Object.values(Enum) |
Нужен массив |
| Размер бандла | Больше | Нулевой |
| Рефакторинг | Проще (одно место) | Нужен поиск по коду |
Объект-константа как альтернатива
// as const объект — лучшее из обоих миров
const Direction = {
Up: "UP",
Down: "DOWN",
Left: "LEFT",
Right: "RIGHT",
} as const;
// Тип из значений объекта
type Direction = (typeof Direction)[keyof typeof Direction];
// "UP" | "DOWN" | "LEFT" | "RIGHT"
// Есть runtime-объект для итерации
Object.values(Direction); // ["UP", "DOWN", "LEFT", "RIGHT"]
// Нет проблем с isolatedModules
// Tree-shaking работает
// Типобезопасно
Паттерны использования
Битовые флаги
enum Permission {
None = 0,
Read = 1 << 0, // 1
Write = 1 << 1, // 2
Execute = 1 << 2, // 4
All = Read | Write | Execute, // 7
}
function hasPermission(userPerms: number, required: Permission): boolean {
return (userPerms & required) === required;
}
const userPerms = Permission.Read | Permission.Write; // 3
console.log(hasPermission(userPerms, Permission.Read)); // true
console.log(hasPermission(userPerms, Permission.Execute)); // false
Discriminated Union с Enum
enum EventType {
Click = "CLICK",
Hover = "HOVER",
Scroll = "SCROLL",
}
type ClickEvent = { type: EventType.Click; x: number; y: number };
type HoverEvent = { type: EventType.Hover; element: string };
type ScrollEvent = { type: EventType.Scroll; offset: number };
type AppEvent = ClickEvent | HoverEvent | ScrollEvent;
function handleEvent(event: AppEvent): void {
switch (event.type) {
case EventType.Click:
console.log(`Click at ${event.x}, ${event.y}`);
break;
case EventType.Hover:
console.log(`Hover on ${event.element}`);
break;
case EventType.Scroll:
console.log(`Scroll to ${event.offset}`);
break;
}
}
Частые ошибки
- Числовые enum принимают любое число — используйте строковые enum для безопасности
- const enum с
isolatedModules: true— не работает в Babel/esbuild, используйте обычный enum - Забыть присвоить значения строковому enum — автоинкремент не работает для строк
- Не знать про tree-shaking — обычные enum создают runtime-объект, который может не вытряхнуться
- Использовать enum для всего — для простых случаев union types проще и легче
// Ошибка: забыл значение для строкового enum
enum Bad {
A = "A",
B, // Ошибка! Enum member must have initializer
}
// Правильно:
enum Good {
A = "A",
B = "B",
}
Практика
- Создайте числовой enum
HttpStatusс основными кодами (200, 404, 500) - Создайте строковой enum
LogLevel(debug, info, warn, error) - Перепишите enum через
as constобъект — сравните поведение - Используйте битовые флаги для системы разрешений
- Создайте discriminated union с enum-дискриминатором
Связанные темы
- Литеральные типы — string literal types как альтернатива
- Union и Intersection — union types для замены enum
- typeof и keyof — извлечение типов из as const объектов
- Type guards — проверка enum-значений в рантайме