Enum в TypeScript — полное руководство
Что такое enum (перечисление)
Enum, или перечисляемый тип, — это структура данных, с помощью которой можно определить набор именованных значений. Enum удобны, когда значение может принимать только ограниченное число вариантов. Вместо «магических» чисел или строк вы даёте значениям осмысленные имена, что повышает читаемость и снижает риск ошибок.
Кратко: enum отображает набор пар «ключ/значение» — ключ всегда строка, а значение по умолчанию — автоинкрементируемое число. Значения могут быть также строками или вычисляемыми выражениями.
Создание enum
Перечисления обычно задают фиксированный набор опций для некоторого параметра. Например, enum, представляющий основные цвета, может содержать Red, Yellow и Blue.
В TypeScript enum создают с помощью ключевого слова enum, затем указывают имя и блок фигурных скобок с членами перечисления. По соглашениям JavaScript/TypeScript имя enum обычно начинается с заглавной буквы.
enum Direction { Up, Down, Left, Right
}
В этом примере объявлено перечисление Direction с четырьмя членами. Поскольку явно не задано значение для членов, TypeScript присвоит им числовые значения, начиная с 0: Up = 0, Down = 1 и т.д. Если хотите — можно явно указать числа:
enum Direction { Up = 0, Down = 1, Left = 2, Right = 3,
}
Или смешивать: задать стартовое значение, а остальные пусть инкрементируются:
enum Status { Active = 9, Inactive, // 10
}
Здесь Inactive автоматически получит значение 10.
Виды enum
TypeScript различает несколько вариантов перечислений по типу значений их членов.
Числовые enum
Это самый распространённый случай: члены перечисления получают числовые значения. Поведение описано выше — при отсутствии инициализатора значения автоинкрементируются.
Когда использовать: когда важно компактное представление в рантайме или когда значения используются в арифметике/индексации.
Строковые enum
Если все члены — строки, то нужно инициализировать каждый член строкой:
enum PrimaryColors { Red = "RED", Yellow = "YELLOW", Blue = "BLUE"
}
Строковые enum удобны при сериализации (например, отправка в JSON) — их значения сразу читаемы.
Гетерогенные enum
Гетерогенные перечисления содержат и строки, и числа:
enum Result { Success = "SUCCESS", Failure = 0
}
TypeScript документация рекомендует избегать гетерогенных enum без веской причины: они усложняют код и повышают шанс ошибок.
Константные и вычисляемые члены
У каждого члена enum есть значение, которое может быть либо константой, либо вычисляемым.
Константные члены
Член считается константным, если выполняется хотя бы одно из условий:
- Это первый член enum и у него нет инициализатора.
- У члена нет инициализатора, и предыдущий член был числовой константой.
- Член инициализирован константным выражением (литерал и т.п.).
Константные выражения можно полностью вычислить во время компиляции. Это улучшает производительность и упрощает отладку, потому что при транспиляции в JavaScript заменяются конкретные значения.
Примеры константных 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"
}
Транспиляция константного строкового enum может выглядеть так:
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)
}
functioncalculateSize(value: number): number{
return value * 5;
}
console.log(Size.Large)
Вычисляемые члены добавляют гибкости, но увеличивают вероятность ошибок во время выполнения, потому что значения неизвестны на этапе компиляции. Транспилированный код содержит вызовы функций, а не их результаты:
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 осуществляется через точечную нотацию:
enum Direction { Up = 0, Down = 1, Left = 2, Right = 3,
}
console.log(Direction.Left) // 2Обратное отображение для числовых enum
В числовых enum TypeScript автоматически создаёт двунаправленное отображение: по имени вы получаете значение, а по значению — имя. Это удобно, когда у вас есть число и нужно узнать соответствующее имя члена.
Пример ручного обратного поиска:
enum Direction { Up = 1, Down, Left, Right
}
functiongetDirectionName(directionValue: number): string{
// Reverse mapping
const directionName = Direction[directionValue];
return directionName;
}
console.log(getDirectionName(1)); // "Up"
console.log(getDirectionName(3)); // "Left"
Обратное отображение не работает для строковых enum — у них нет автоматического обратного соответствия.
Сценарии применения
Enum применяют в самых разных ситуациях:
- Переключатели (switch) и ветвления по фиксированным состояниям.
- Параметры функций и интерфейсы API для ограничения допустимых значений.
- Карты сопоставления между кодом и читаемыми метками (например, коды ошибок).
- Представление состояний конечного автомата или стадий процесса.
Enum повышают понятность и упрощают рефакторинг: IDE может подсказать все места использования члена, а компилятор поймает опечатки в именах.
Когда enum не подходит
Enum полезны, но не всегда оптимальны. Примеры, когда их лучше не использовать:
- Множество динамически меняющихся значений (например, список категорий из БД). В таких случаях используйте типы строк (union of strings) или объектную структуру, загружаемую из данных.
- Когда важна только сериализация и значения определяются вне кода — храните строки в конфиге/БД и используйте тип-проверки на уровне приложения.
- Когда нужен строгий сопоставимый набор значений во внешних API с возможностью расширения без деплоя — лучше применять версионирование API или схему расширяемых тегов.
Counterexample: если список статусов приходит из сервера и может меняться, жестко зашитый enum приведёт к рассинхронизации.
Альтернативные подходы
Union типов строк (string literal union):
- type Status = “active” | “inactive” | “pending”;
- Преимущество: проще встраивать в типы и легко сериализовать.
Константные объекты:
- const Status = { Active: “ACTIVE”, Inactive: “INACTIVE” } as const;
- type Status = typeof Status[keyof typeof Status];
- Позволяет использовать объектные операции и маппинг.
Использовать Tagged unions (sum types) для сложных состояний с данными.
Выбор зависит от требований: нужен ли двунаправленный mapping, нужна ли компактность в рантайме, важна ли читаемость в логах.
Психические модели и эвристики
- Если значения фиксированы и не изменятся после релиза — enum уместен.
- Если важна читаемость логов и API — используйте строковые enum или константные строки.
- Для небольшой динамики используйте объектные константы и union-тип.
- Избегайте гетерогенных enum без жёсткой причины.
Эти простые правила помогут выбрать правильную структуру данных.
Практическая шпаргалка (cheat sheet)
- Объявление: enum Name { A, B, C }
- Явное присвоение: A = 1
- Строки: A = “A”
- Двунаправленный мэппинг: работает только для чисел
- Константный член: литерал или вычислим из предыдущего числового
- Вычисляемый член: использует вызов функции — значение в рантайме
Быстрая проверка перед использованием enum:
- Меняется ли набор значений? Если да — enum может быть нежелателен.
- Нужно ли обратное маппирование? Если да — используйте числовые enum.
- Требуется ли сериализация в человекочитаемом виде? Если да — строковые enum.
Чеклисты по ролям
Для разработчика:
- Проверил, изменится ли набор значений со временем.
- Выбрал строковой enum для удобной сериализации.
- Добавил комментарий/пример использования для каждого члена.
- Написал юнит-тесты на граничные случаи (неизвестное значение).
Для архитектора:
- Оценил влияние enum на контракт API.
- Решил способ версионирования при расширении набора значений.
- Выбрал между enum и динамической конфигурацией.
Критерии приёмки
- Перечисление покрывает все допустимые состояния доменной области.
- Наличие тестов: проверка сериализации и обработки неизвестных значений.
- Нет жёстких зависимостей на значения enum в бизнес-логике (используйте имена, а не числа).
- Документация и комментарии для нестандартных значений и гетерогенных enum.
Частые ошибки и крайние случаи
- Ожидание обратного мэппинга для строковых enum. Это не сработает.
- Использование enum для динамических списков — приводит к багам при обновлениях.
- Смешение семантик: давать одному enum как смысловые ярлыки, так и машинные коды — усложняет поддержку.
Пример ошибки: сравнивать значение с числом, а затем сериализовать в API как строку — рассинхронизация возможна.
Безопасность и приватность
Enum сами по себе не добавляют риск безопасности. Но при использовании строковых enum в API убедитесь, что передаваемые значения проходят проверку на сервере. Не полагайтесь только на enum на клиенте как на защитный механизм.
Короткий глоссарий (1 строка каждой записи)
- Enum: перечисление фиксированных именованных значений.
- Член enum: отдельный элемент перечисления (ключ/значение).
- Константный член: значение, известно на этапе компиляции.
- Вычисляемый член: значение определяется в рантайме.
- Гетерогенный enum: сочетание чисел и строк.
Примеры использования в реальном коде
- Состояния UI: Loading, Success, Error.
- Коды ошибок внутри приложения.
- Параметры конфигурации, где набор заранее известен.
Итог
Enum в TypeScript — это удобный инструмент для моделирования конечных множеств значений. Выбирайте между числовым и строковым видом в зависимости от требований к сериализации и уровню строгой типизации. Избегайте гетерогенных enum без необходимости и заменяйте enum на union строк или константные объекты, если набор значений динамичен.
Важно: перед введением enum подумайте о будущем расширении набора значений и о том, как это скажется на API и конфигурации.
Краткие выводы:
- Enum делают код выразительнее.
- Числовые enum дают автоматическое обратное отображение.
- Строковые enum удобнее для сериализации.
- При динамичных данных лучше использовать другие подходы.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone