Перечисления (enum) в TypeScript — руководство и лучшие практики

Введение
Enum (перечисление) — это структура данных, позволяющая определить набор именованных значений. Она помогает делать код более выразительным и самодокументированным: вместо «магических» чисел или строк вы используете понятные имена.
Короткое определение: enum — именованный набор констант, каждая из которых имеет ключ (строка) и значение (число, строка или вычисляемое выражение).
Важно: имя enum обычно пишут с заглавной буквы по соглашениям JavaScript/TypeScript.
Создание перечисления
Обычно перечисление описывает ограниченное множество вариантов. Например, перечисление для направлений или статусов.
Общий синтаксис в TypeScript:
enum Direction {
Up,
Down,
Left,
Right
}Если явно не указывать значения, TypeScript автоматически присвоит числовые значения: первый член получит 0, следующий — 1 и так далее.
Явное присвоение:
enum Direction {
Up = 0,
Down = 1,
Left = 2,
Right = 3,
}Частичное явное присвоение — значение после инициализированного члена продолжит автоинкремент:
enum Status {
Active = 9,
Inactive, // 10
}Это поведение применяется только к числовым членам, не к строковым.
Типы перечислений
Enums в TypeScript можно условно разделить на три вида:
- числовые (numeric enums)
- строковые (string enums)
- смешанные (heterogeneous enums)
Числовые перечисления
Это поведение, которое мы показали выше: члены получают числовые значения и могут автоматически инкрементироваться.
Пример:
enum Weekday {
Monday = 1,
Tuesday,
Wednesday,
Thursday,
Friday
}Строковые перечисления
Если все члены — строки, их нужно инициализировать явно. Это удобно при сериализации и логировании:
enum PrimaryColors {
Red = "RED",
Yellow = "YELLOW",
Blue = "BLUE"
}Строковые значения более самодоказывающие и часто предпочтительнее в API.
Смешанные перечисления
Смешанные enum содержат и строковые, и числовые члены:
enum Result {
Success = "SUCCESS",
Failure = 0
}TypeScript документация не рекомендует часто использовать смешанные enum, так как они повышают сложность и вероятность ошибок.
Константные и вычисляемые члены
Каждый член enum может быть константным или вычисляемым.
Коротко:
- Константный член вычисляется на этапе компиляции.
- Вычисляемый член определяется выражением, которое будет выполнено в рантайме.
Когда член считается константным
Член enum считается константным, если он удовлетворяет одному из условий:
- это первый член и у него нет инициализатора;
- он не имеет инициализатора, а предыдущий член был числовой константой;
- он инициализирован с помощью константного выражения (строковый или числовой литерал, либо выражение, которое может быть вычислено на этапе компиляции).
Примеры константных enum:
// CASE 1
enum Direction {
Up,
Down,
Left,
Right
}
// CASE 2
enum Weekday {
Monday = 1,
Tuesday,
Wednesday,
Thursday,
Friday
}
// CASE 3
enum Season {
Spring = "SPRING",
Summer = "SUMMER",
Autumn = "AUTUMN",
Winter = "WINTER"
}При трансляции в JavaScript константные члены разворачиваются в их литеральные значения, что улучшает производительность и упрощает отладку.
Пример транспиляции для Season:
var Season;
(function (Season) {
Season["Spring"] = "SPRING";
Season["Summer"] = "SUMMER";
Season["Autumn"] = "AUTUMN";
Season["Winter"] = "WINTER";
})(Season || (Season = {}));Вычисляемые члены
Если значение члена задаётся результатом функции или выражения, оно будет вычислено в рантайме:
enum Size {
Small = 1,
Medium = calculateSize(12),
Large = calculateSize(5)
}
function calculateSize(value: number): number {
return value * 5;
}
console.log(Size.Large);Транспилированный код сохраняет вызов функции, поэтому конечные значения определяются уже в JavaScript во время выполнения:
var Size;
(function (Size) {
Size[Size["Small"] = 1] = "Small";
Size[Size["Medium"] = calculateSize(12)] = "Medium";
Size[Size["Large"] = calculateSize(5)] = "Large";
})(Size || (Size = {}));
console.log(Size.Large);Последний пример подчёркивает: вычисляемые члены увеличивают вероятность ошибок времени выполнения и усложняют статический анализ.
Доступ к значениям и обратная проекция
Доступ к членам enum осуществляется через точечную нотацию:
enum Direction {
Up = 0,
Down = 1,
Left = 2,
Right = 3,
}
console.log(Direction.Left); // 2Обратная проекция для числовых enum
Числовые enum поддерживают обратную проекцию: можно получить имя члена по его значению. Это удобно при декодировании числовых состояний:
enum Direction {
Up = 1,
Down,
Left,
Right
}
function getDirectionName(directionValue: number): string {
const directionName = Direction[directionValue];
return directionName;
}
console.log(getDirectionName(1)); // "Up"
console.log(getDirectionName(3)); // "Left"Обратной проекции нет для строковых enum — там нужно делать поиск через ключи или маппинг.
Когда использовать enum и альтернативы
Enum полезны, когда у вас есть ограниченный набор вариантов, и вы хотите:
- улучшить читаемость кода;
- централизовать значения для переключателей (switch) и условий;
- сохранить согласованность API и сериализации.
Альтернативы enum:
- union типов строк: type Status = “active” | “inactive”; — компиляция лучше, меньше кода в рантайме;
- const objects: const Status = { Active: “ACTIVE” } as const; type Status = typeof Status[keyof typeof Status]; — даёт автодополнение и более компактный рантайм;
- библиотеки с перечислениями/флагами, если нужны дополнительные механизмы сериализации/валидции.
Пример union типа:
type Status = "ACTIVE" | "INACTIVE";
function setStatus(s: Status) { /* ... */ }Преимущество union: меньше магии рантайма, лучше оптимизируется и проще сериализуется.
Практические рекомендации
- Предпочитайте строковые enum или union типов для публичных API и сериализации.
- Избегайте смешанных enum, если нет острой нужды.
- Минимизируйте вычисляемые члены — они усложняют анализ и тестирование.
- Для производительности используйте const enum, если проект и сборка позволяют.
Примечание об использовании const enum:
const enum — специальная форма, которая полностью инлайнится при компиляции, но требует включённой опции компилятора и невозможности рефлексии на рантайме.
Миграция и совместимость
Если вы мигрируете с JavaScript или с plain objects на enum:
- Оцените, нужна ли обратная проекция: если да — используйте числовой enum.
- Если важна читаемость и API — используйте строковые значения или union типов.
- Проведите тесты сериализации/десериализации и интеграционные тесты.
Короткие советы:
- при использовании библиотек сериализации (JSON, protobuf) отдавайте предпочтение строкам;
- при взаимодействии с базами данных старайтесь хранить значения, удобные для чтения и миграции.
Контроль качества: чек-лист перед ревью
- Является ли набор значений фиксированным?
- Подходит ли строковый enum или union для API?
- Нет ли вычисляемых членов без необходимости?
- Не использован ли смешанный enum, где можно обойтись одним типом?
- Обработана ли сериализация/десериализация в тестах?
Мини-методология для выбора типа enum
- Определите требования: нужен ли индентификатор (число) или человекочитаемое значение (строка).
- Если нужен компактный рантайм и обратная проекция — выберите числовой enum.
- Для API и логов — строковый enum или union типов.
- Если важно ноль-копий в рантайме — рассмотрите const enum и проверьте сборку.
Дерево решений (Mermaid)
graph TD
A[Нужно ли обратное отображение по значению?] -->|Да| B[Числовой enum]
A -->|Нет| C[Нужно ли человекочитаемое значение в API?]
C -->|Да| D[Строковый enum или union типов]
C -->|Нет| E[const enum или объекты с as const]Роль‑ориентированные чек-листы
Для разработчика:
- использовать понятные имена членов;
- избегать магических чисел вне enum;
- написать модульные тесты на сериализацию.
Для ревьювера:
- проверить необходимость enum vs union;
- оценить влияние на bundle size;
- убедиться в корректной обработке ошибок при неизвестных значениях.
Для архитектора:
- стандартизировать формат enum в кодовой базе (строки vs числа);
- задокументировать рекомендации для API.
Критерии приёмки
- Код использует enum только там, где набор значений фиксирован и документирован.
- Все публичные API используют человекочитаемые значения (строки) или ясно задокументированы.
- Нет неинициализированных вычисляемых членов без тестов.
- Сборка и тесты не ломаются при включении const enum.
Частые ошибки и когда enum не работает
- Использование смешанных enum для простых наборов значений — приводит к путанице.
- Полагаться на вычисляемые значения в рантайме там, где нужен статический анализ.
- Не тестировать сериализацию/десериализацию при изменении значений enum.
Короткий глоссарий
- enum: перечисление именованных констант.
- константный член: значение, вычисляемое на этапе компиляции.
- вычисляемый член: значение, вычисляемое в рантайме.
- обратная проекция: получение имени члена по его числовому значению.
Примеры тест-кейсов
- Проверка, что Direction.Left === 2 при определённых значениях.
- Проверка сериализации/десериализации строковых enum.
- Тест на поведение при передаче неизвестного числового значения в функцию, делающую обратную проекцию.
Риски и смягчение
- Риск: изменение числовых значений сломает клиентов. Смягчение: использовать строки в публичных API.
- Риск: вычисляемые члены добавляют ошибки рантайма. Смягчение: избегать вычисляемых членов без тестов.
Заключение
Перечисления в TypeScript — мощный инструмент для выражения фиксированных наборов значений. Правильный выбор между числовыми и строковыми enum, осторожность с вычисляемыми и смешанными членами, а также стандартизация практик в проекте помогут сохранить читаемость, безопасность и совместимость кода.
Важно: перед принятием решения оцените последствия для API, bundle size и тестируемости.
Полезное напоминание:
Important: если требуется совместимость с внешними системами или человекочитаемость логов — выбирайте строковые значения или union типов.
Notes: const enum инлайнится компилятором и экономит место, но исключает возможности рефлексии.
FAQ
Что лучше для API: enum или union типов?
Для внешних API обычно лучше использовать строковые enum или union типов (например, type Status = “ACTIVE” | “INACTIVE”), потому что строки проще отлаживать и безопаснее при изменениях.
Можно ли использовать enum в JSON при передаче данных в сеть?
Да, но предпочтительнее использовать строковые enum или вручную сериализовать числовые enum в понятные строки, чтобы не ломать клиентов при изменении значений.
Что такое const enum и когда его применять?
const enum — это enum, который полностью инлайнится в финальный код при компиляции. Применяйте его для уменьшения размера бандла, если не нужна динамическая рефлексия по enum.
Похожие материалы
Группы вкладок Safari на iPhone и iPad
Как принудительно завершить приложение на Mac
Как отправлять вкладки Chrome между устройствами
Настройка стартовой страницы Safari на Mac
Профили в Safari на iPhone и iPad — настройка