Сегодня нас ждет очень интересная и полезная тема, перечисления или Enums. Если вы когда-либо мечтали о том, чтобы ваши константы были не просто разрозненными числами или строками, а были объединены в логическую, удобную для чтения и поддержки структуру, то этот урок именно для вас. Enums это один из тех инструментов, который по-настоящему показывает мощь TypeScript поверх JavaScript, добавляя ясности и надёжности нашему коду.
Мы с вами уже изучили базовые типы, функции и интерфейсы. Мы научились описывать форму объектов. Но как быть, когда нам нужно описать не форму, а некий ограниченный набор возможных значений? Например, дни недели, статусы заказов, права доступа, направления движения (север, юг, запад, восток). Именно для таких задач и существуют перечисления. В этом уроке мы детально разберём, что такое enum, как их создавать и как ими пользоваться, рассмотрим два основных их типа: числовые и строковые. А в конце, как всегда, нас ждут практические задачи для закрепления материала.
Что такое перечисления (Enum)?
Давайте начнём с простого примера, чтобы понять проблему, которую решают перечисления. Представьте, что вы работаете с системой, где статус заказа может быть одним из трёх чисел: 0 (новый), 1 (в обработке) или 2 (выполнен).
Без перечислений ваш код мог бы выглядеть примерно так:
let orderStatus = 1; // 1 означает "в обработке" if (orderStatus === 1) { // что-то делаем }
Что не так с этим подходом? Код становится несамодокументированным. Цифра 1 сама по себе ни о чём не говорит. Через месяц вы, а тем более другой разработчик, посмотрев на этот код, обязательно задастся вопросом: «А что такое 1? Что оно значит?». Приходится держать в голове или искать в документации соответствие этих «магических чисел» их настоящему значению. Это prone to errors (склонно к ошибкам). Можно описать константы, чтобы избежать магических чисел:
const ORDER_STATUS_NEW = 0; const ORDER_STATUS_PROCESSING = 1; const ORDER_STATUS_COMPLETED = 2; let orderStatus = ORDER_STATUS_PROCESSING; if (orderStatus === ORDER_STATUS_PROCESSING) { // теперь понятнее! }
Уже лучше! Код стал читаемее. Но и у этого подхода есть недостатки. Во-первых, это не единая конструкция, а набор разрозненных констант. Во-вторых, нет никакой связи между этими константами, кроме нашего собственного соглашения о именовании. TypeScript предлагает более элегантное, структурированное и типобезопасное решение Enum.
Enum (перечисление) это конструкция, которая позволяет определить набор именованных констант. Иными словами, это тип данных, который содержит конечный набор возможных значений, каждому из которых присвоено понятное человеку имя. Используя enum, мы создаём новый тип и сразу набор значений для него. Это делает код:
-
Читаемым:
OrderStatus.Processingочевидно понятнее, чем1. -
Поддерживаемым: если нужно изменить значение, это делается в одном месте, в объявлении enum.
-
Безопасным: TypeScript будет проверять, что мы используем только допустимые значения перечисления и подсказывать их через автодополнение.
Вот как та же задача решается с помощью enum:
enum OrderStatus { New = 0, Processing = 1, Completed = 2 } let currentStatus: OrderStatus = OrderStatus.Processing; if (currentStatus === OrderStatus.Processing) { // Код абсолютно прозрачен! console.log("Заказ готовится к отправке..."); }
Согласитесь, так выглядит гораздо профессиональнее и надёжнее? Теперь давайте разберёмся, как именно создавать эти перечисления.
Синтаксис enum для создания наборов именованных констант
Синтаксис объявления enum очень прост и интуитивно понятен. Мы используем ключевое слово enum, за которым следует имя перечисления. Имя, как и у интерфейсов и классов, принято писать в PascalCase (с заглавной буквы каждого слова). Затем в фигурных скобках {} мы перечисляем элементы через запятую.
Базовый синтаксис:
enum ИмяПеречисления { Элемент1, Элемент2, Элемент3 }
Каждый элемент enum по умолчанию получает числовое значение, автоматически инкрементируемое с нуля. То есть первому элементу присваивается 0, второму 1, третьему 2 и так далее. Это самый распространённый вариант использования, который так и называется числовое перечисление.
Пример с днями недели:
enum Weekday { Monday, // 0 Tuesday, // 1 Wednesday, // 2 Thursday, // 3 Friday, // 4 Saturday, // 5 Sunday // 6 } let today: Weekday = Weekday.Wednesday; console.log(today); // Выведет: 2 console.log(Weekday[2]); // Выведет: "Wednesday"
Обратите внимание на последнюю строку: Weekday[2]. У числовых enum есть одна крутая особенность reverse mapping (обратное отображение). Это значит, что компилятор TypeScript не только присваивает имя числу (Weekday.Wednesday -> 2), но и генерирует обратное соответствие: число к имени (Weekday[2] -> "Wednesday"). Это бывает крайне полезно, например, когда вы получили из базы данных число 2 и хотите преобразовать его в понятное строковое представление для логирования или отображения пользователю.
Но что, если мы хотим начать нумерацию не с нуля? Или нам нужно явно задать конкретные значения для некоторых элементов? Это легко сделать.
enum HttpStatusCode { OK = 200, Created = 201, BadRequest = 400, Unauthorized = 401, NotFound = 404, InternalServerError = 500 } let responseCode: HttpStatusCode = HttpStatusCode.NotFound; console.log(responseCode); // Выведет: 404 // Использование в функции function handleResponse(code: HttpStatusCode) { if (code === HttpStatusCode.OK) { console.log("Запрос успешен!"); } else if (code === HttpStatusCode.NotFound) { console.log("Ресурс не найден."); } } handleResponse(HttpStatusCode.NotFound);
В этом примере мы создали перечисление для HTTP-статусов, явно указав их реальные числовые коды. Это идеальный сценарий для числовых enum.
Можно также задать значение только для первого элемента, а последующие будут автоматически увеличиваться на единицу:
enum Priority { Low = 1, // 1 Medium, // 2 (1 + 1) High // 3 (2 + 1) } console.log(Priority.Medium); // Выведет: 2
Как видите, синтаксис прост и гибок. Но мир разработки не всегда крутится вокруг чисел. Часто мы хотим использовать понятные строки. Для этого в TypeScript существуют строковые перечисления.
Числовые Enum (Numeric Enums)
Мы уже практически полностью разобрали числовые enum в предыдущем разделе, но давайте структурируем знания и углубимся в детали.
Числовые enum это перечисления, значения которых являются числами. Они являются наиболее часто используемым типом enum в TypeScript. Их главные особенности:
-
Авто-инкремент: Если элемент не имеет явно указанного значения, он будет равен предыдущему элементу + 1. Если это первый элемент, он получит значение
0. -
Обратное отображение: Во время компиляции генерируется объект, который позволяет по числовому значению получить строковое имя элемента (как в примере с
Weekday[2]). -
Вычисляемые и константные значения: Значения могут быть не только простыми числами, но и результатом вычислений (но с некоторыми ограничениями).
Давайте рассмотрим пример с вычисляемым значением. Вы можете задать значение элемента, используя простое математическое выражение.
enum FilePermissions { Read = 1, // 1 (0b001 в двоичной) Write = 2, // 2 (0b010) Execute = 4, // 4 (0b100) ReadWrite = Read | Write // 3 (0b011) - побитовое ИЛИ } console.log(FilePermissions.ReadWrite); // Выведет: 3 // Это часто используется для проверки прав с помощью побитовых операторов. function checkPermission(userPermissions: FilePermissions, required: FilePermissions): boolean { return (userPermissions & required) === required; } let myPermissions: FilePermissions = FilePermissions.Read | FilePermissions.Write; // 3 console.log(checkPermission(myPermissions, FilePermissions.Read)); // true console.log(checkPermission(myPermissions, FilePermissions.Execute)); // false
Это мощный паттерн, часто используемый для работы с битовыми масками (флагами).
Важно помнить: если вы используете вычисляемое значение (как Read | Write в примере выше), то для следующих за ним элементов нельзя опускать инициализатор, так как компилятор не сможет вычислить авто-инкремент.
enum Example { A = 1 + 1, // Вычисляемое значение (2) B, // Ошибка! Компилятор не знает, какое значение здесь должно быть. C = 3 // Так можно, значение задано явно. }
Чтобы исправить это, нужно явно задать значение для B:
enum Example { A = 1 + 1, // 2 B = 3, // Теперь всё в порядке C // 4 (3 + 1) }
Числовые enum это отличный выбор, когда ваши значения по своей природе являются числами (коды ошибок, статусы, флаги, приоритеты) или когда вам не важно конкретное значение, а важна лишь уникальность.
Строковые Enum (String Enums)
Строковые enum это перечисления, значения которых являются строками. Они появились в TypeScript позже числовых и предлагают иной набор преимуществ и особенностей.
Объявляются они аналогично, но каждому элементу must be явно присвоено строковое значение.
enum Direction { North = "NORTH", East = "EAST", South = "SOUTH", West = "WEST" } let myDirection: Direction = Direction.North; console.log(myDirection); // Выведет: "NORTH"
Главное отличие и особенность строковых enum, что у них нет обратного отображения. Direction["NORTH"] не будет существовать. Это имеет смысл, потому что значение уже является читаемой строкой. Direction.North уже содержит значение "NORTH", поэтому необходимость в обратном преобразовании отпадает.
Зачем же тогда они нужны, если можно просто использовать строковые константы?
-
Типобезопасность: Как и все enum, строковые enum определяют свой собственный тип. Это означает, что вы не сможете присвоить переменной типа
Directionлюбую строку, а только одну из четырёх конкретных:"NORTH","EAST","SOUTH"или"WEST".let dir: Direction = "NORTH"; // Ошибка! Тип '"NORTH"' не может быть присвоен типу 'Direction'.
Это ловит огромное количество опечаток на этапе компиляции.
-
Явность и «одно место для изменений»: Если вы захотите изменить значение, скажем, с
"NORTH"на"N", вам нужно будет сделать это только в одном месте, в объявлении enum. Во всём остальном коде будет использоватьсяDirection.Northи после изменения значения enum компилятор сам позаботится об обновлении всех мест использования. -
Автодополнение (IDE support): При работе в редакторе кода (VSCode, WebStorm) вам будет предлагаться автодополнение по
Direction., что предотвращает ошибки и ускоряет разработку.
Строковые enum идеально подходят для:
-
Сериализации данных: Когда вам нужно отправлять значения в виде понятных строк по сети (в API, в базу данных, в файл лога). Значение
"COMPLETED"в JSON-ответе API гораздо понятнее, чем загадочное число2. -
Сравнений во время выполнения: Сравнивать строки так же быстро, как и числа, в современных JS-движках.
-
Работы с динамическими значениями, которые по своей природе являются строками.
Пример использования строкового enum для статусов:
enum UserAction { Add = "USER_ADD", Edit = "USER_EDIT", Delete = "USER_DELETE" } function logAction(action: UserAction) { console.log(`Выполнено действие: ${action}`); // В лог попадёт понятная строка, например "Выполнено действие: USER_ADD" } logAction(UserAction.Add); // А если мы получили строку извне (например, из события), мы можем проверить её валидность. const incomingEvent: string = "USER_EDIT"; // Допустим, пришло из библиотеки UI // Для проверки нужно будет использовать Object.values() if (Object.values(UserAction).includes(incomingEvent as UserAction)) { logAction(incomingEvent as UserAction); // Преобразуем тип и используем } else { console.warn("Неизвестное действие!"); }
Важно отметить, что в строковых enum нельзя использовать вычисляемые значения так же свободно, как в числовых. Каждое значение должно быть строковым литералом или выражением, вычисляемым в compile-time из других строковых enum.
enum Base { Value = "Hello" } enum Derived { // Можно так: Greeting = Base.Value, // "Hello" // И даже так: World = Greeting + " World!", // "Hello World!" (т.к. 'Greeting' - константа из того же enum) } // А так НЕЛЬЗЯ: function getString() { return "test"; } enum ErrorExample { // Test = getString() // Ошибка! Вычисляемое значение, не определяемое во время компиляции. }
Гетерогенные Enum
Технически TypeScript позволяет создавать mixed enums (смешанные перечисления), где часть элементов имеет числовые значения, а часть строковые.
WeirdEnum { No = 0, Yes = "YES" }
Мой вам настоятельный совет: НЕ ДЕЛАЙТЕ ТАК. Хотя это и возможно, такая практика считается антипаттерном. В этом нет никакого практического смысла, это только запутывает код и тех, кто будет его читать и поддерживать. Всегда выбирайте один тип для перечисления: либо числа, либо строки.
Const Enums (Константные перечисления)
Для полного понимания картины стоит упомянуть о const enum. Объявляются они с помощью ключевого слова const:
const enum Size { Small, Medium, Large } let mySize = Size.Medium;
Особенность const enum в том, что они полностью удаляются во время компиляции. В скомпилированный JavaScript-код не попадает никакой объект Size. Вместо этого, каждое использование элемента enum (например, Size.Medium) заменяется на его непосредственное значение (в данном случае 1).
Плюсы:
-
Полное отсутствие накладных расходов во время выполнения. Нет генерируемого объекта, экономится память.
-
Немного более производительный код.
Минусы:
-
Нет обратного отображения. Для числовых const enum нельзя сделать
Size[1], потому что объектаSizeне существует в рантайме. -
Требуют особых настроек компилятора (
preserveConstEnums) для отладки, иначе в отладочной информации тоже не будет объекта.
На начальном этапе я рекомендую использовать обычные (non-const) enum, чтобы избежать потенциальных сложностей с отладкой. К const enum можно прибегать позже, для оптимизации критических к производительности участков кода.
Практические примеры и задачи
Давайте закрепим знания на реальных примерах и задачах.
Пример 1: Управление состоянием UI
Представьте, что у вас есть кнопка, которая может быть в нескольких состояниях.
enum ButtonState { Default = "default", Loading = "loading", Success = "success", Error = "error" } class UIButton { private state: ButtonState = ButtonState.Default; setState(newState: ButtonState) { this.state = newState; this.updateStyles(); } private updateStyles() { // Убираем все предыдущие классы // Добавляем класс в зависимости от state console.log(`Applying CSS class: button--${this.state}`); } } const myButton = new UIButton(); myButton.setState(ButtonState.Loading); // Applying CSS class: button--loading // Через 2 секунда... myButton.setState(ButtonState.Success); // Applying CSS class: button--success
Задача 1: Дни недели
Условие: Создайте числовое перечисление Weekday с днями недели (понедельник — воскресенье). Напишите функцию getDayStatus(day: Weekday): string, которая возвращает:
-
"Рабочий день"для понедельника — пятницы. -
"Выходной день"для субботы и воскресенья.
Решение:
enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday } function getDayStatus(day: Weekday): string { if (day === Weekday.Saturday || day === Weekday.Sunday) { return "Выходной день"; } else { return "Рабочий день"; } } // Проверка console.log(getDayStatus(Weekday.Monday)); // Рабочий день console.log(getDayStatus(Weekday.Sunday)); // Выходной день // Более элегантное решение с диапазоном function getDayStatusImproved(day: Weekday): string { if (day >= Weekday.Monday && day <= Weekday.Friday) { return "Рабочий день"; } else { return "Выходной день"; } }
Задача 2: Роли пользователей
Условие: Вы разрабатываете систему с ролями пользователей: Admin, Editor, Author, Subscriber. Создайте строковое перечисление UserRole. Напишите функцию canEditContent(role: UserRole): boolean, которая возвращает true только для Admin и Editor.
Решение:
enum UserRole { Admin = "ADMIN", Editor = "EDITOR", Author = "AUTHOR", Subscriber = "SUBSCRIBER" } function canEditContent(role: UserRole): boolean { // Проверяем, является ли роль одной из разрешённых return role === UserRole.Admin || role === UserRole.Editor; } // Альтернативное решение с использованием массива function canEditContentAlt(role: UserRole): boolean { const allowedRoles: UserRole[] = [UserRole.Admin, UserRole.Editor]; return allowedRoles.includes(role); } // Проверка console.log(canEditContent(UserRole.Admin)); // true console.log(canEditContent(UserRole.Author)); // false
Задача 3: Направление движения
Условие: Создайте перечисление CompassDirection (North, East, South, West). Напишите функцию move(currentPosition: [number, number], direction: CompassDirection, steps: number): [number, number], которая принимает текущие координаты (x, y), направление и количество шагов. Функция должна возвращать новые координаты. Предположим, что движение на North увеличивает Y, на East увеличивает X и т.д.
Решение:
enum CompassDirection { North = "NORTH", East = "EAST", South = "SOUTH", West = "WEST" } function move(currentPosition: [number, number], direction: CompassDirection, steps: number): [number, number] { let [x, y] = currentPosition; // Деструктуризация массива switch (direction) { case CompassDirection.North: y += steps; break; case CompassDirection.East: x += steps; break; case CompassDirection.South: y -= steps; break; case CompassDirection.West: x -= steps; break; // default не нужен, т.к. TypeScript проверит, что все варианты обработаны } return [x, y]; } // Проверка let position: [number, number] = [0, 0]; position = move(position, CompassDirection.East, 5); console.log(position); // [5, 0] position = move(position, CompassDirection.North, 3); console.log(position); // [5, 3]
Заключение
Вы узнали, как с помощью ключевого слова enum создавать читаемые и надёжные наборы констант. Мы подробно изучили два основных типа, числовые enum с их авто-инкрементом и обратным отображением и строковые enum, которые идеальны для сериализации и человеко-читаемых значений.
Главное преимущество enum, это способность делать код самодокументируемым и менее подверженным ошибкам. Вместо «магических» чисел и строк повсюду в коде у вас появляются понятные именованные конструкции типа OrderStatus.Completed или UserRole.Admin.
Если вы хотите погрузиться в изучение TypeScript основательно и структурированно, рекомендую пройти весь мой курс TypeScript для начинающих, где мы шаг за шагом, от простого к сложному, разбираем все возможности этого мощного языка.
Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.


