Skip to main content

Що таке enum у TypeScript

Enum у TypeScript - це набір іменованих констант, який компілюється в JavaScript-об'єкт. Замість "магічних" рядків і чисел розкиданих по коду ти отримуєш безпечні типи і зрозумілі імена.

Теорія

TL;DR

  • Enum схожий на список контактів: телефонуєш по імені, а не по номеру. TypeScript стежить, щоб використовувались тільки дійсні імена.
  • Числові enum автоматично інкрементуються (0, 1, 2...) і підтримують зворотнє відображення. Рядкові enum вимагають явних значень і зворотнього відображення не мають.
  • const enum підставляє значення в код під час компіляції. Жодного об'єкта в рантаймі.
  • Для фіксованих наборів (статуси, ролі, напрямки) - enum. Для даних ззовні - union types.

Короткий приклад

typescript
enum Status { Pending = 0, InProgress = 1, Done = 2 } const task: Status = Status.Done; // типобезпечно console.log(task); // 2 console.log(Status[2]); // 'Done' (зворотнє відображення) // Рядковий enum enum Role { Admin = 'admin', User = 'user' } const userRole: Role = Role.Admin; console.log(userRole); // 'admin' (зворотнього відображення немає)

Числові enum підтримують зворотнє відображення - Status[2] повертає 'Done'. Рядкові не підтримують. Саме ця різниця визначає більшість компромісів нижче.

Головна різниця

Числовий enum компілюється в об'єкт з двосторонніми записами: ім'я -> значення і значення -> ім'я. Тому Status[2] повертає 'Done'. Рядковий enum зберігає тільки пряме відображення, що робить його зручнішим для JSON і API. Ціна питання - немає зворотнього пошуку.

Коли що використовувати

  • Числовий enum: стани гри, HTTP-статус-коди, ідентифікатори в БД де може знадобитись зворотнє відображення
  • Рядковий enum: ролі користувачів, рівні дозволів, типи подій що серіалізуються в JSON або передаються через API
  • const enum: якщо важлива продуктивність і потрібні вбудовані значення без об'єкта в рантаймі
  • Union types замість enum: коли значення приходять ззовні або потрібне легше рішення лише для компайл-тайму

Як TypeScript компілює enum

TypeScript компілює числовий enum в JavaScript-об'єкт з двома наборами записів: Status['Pending'] = 0 і Status[0] = 'Pending'. Рядкові enum отримують тільки пряме відображення. const enum окремий випадок - компілятор підставляє кожне звертання до члена enum як літеральне значення і жодного об'єкта не генерує. Тому const myColor = Color.Red стає const myColor = 0 у вихідному коді.

Типові помилки

Помилка 1: Object.values() на числовому enum повертає більше ніж числа

typescript
enum Priority { Low, // 0 Medium, // 1 High // 2 } console.log(Object.values(Priority)); // [0, 1, 2, 'Low', 'Medium', 'High'] - обидва напрямки включені

Числовий enum зберігає і { 0: 'Low', Low: 0, ... }, тому Object.values() повертає шість елементів, а не три. Рішення: фільтрувати по типу або перейти на рядковий enum.

Помилка 2: Object.values() на const enum в рантаймі

typescript
const enum Status { Active = 'active', Inactive = 'inactive' } const list = Object.values(Status); // Помилка в рантаймі: Status не існує

const enum не має об'єкта в рантаймі. Якщо потрібна ітерація, використовуй звичайний enum.

Помилка 3: Числові та рядкові значення в одному enum

typescript
// Погано: зворотнє відображення ламається для числових членів enum Mixed { Success = 1, Error = 'error' }

Вибирай один тип. Змішані enum ламають зворотнє відображення і важко читаються.

Помилка 4: Enum для даних з зовнішнього API

typescript
enum Role { Admin = 'Admin', // велика літера User = 'User' } const fromApi = { role: 'admin' }; // API повертає з маленької літери fromApi.role === Role.Admin; // false - ніколи не збігається

Більшість помилок "enum не працює з API" зводяться до такого ж регістру символів. Для даних з API краще підходять union types (type Role = 'admin' | 'user'). Або нормалізуй вхідне значення перед порівнянням.

Де зустрічається в реальних проектах

  • React/Redux: типи екшенів (enum ActionType { FETCH_START = 'FETCH_START' })
  • Express/Node.js: HTTP-статус-коди, типи помилок, стани підключення до БД
  • NestJS: декоратори для контролю доступу на основі ролей використовують enum для рівнів дозволів
  • GraphQL: enum-типи в схемі напряму відображаються на TypeScript enum
  • Ігрові рушії: Phaser і Babylon.js використовують enum для станів гри, клавіш і типів колізій

Питання на співбесіді

Q: Яка різниця між enum і union type на кшталт type Status = 'pending' | 'done'?


A: Enum створює об'єкт в рантаймі і дає іменовану групу яку можна використовувати по всій кодовій базі. Union types існують тільки під час компіляції, легші в бандлі і краще підходять для значень що приходять ззовні. Enum обирай коли потрібна ітерація або зворотнє відображення. Union types - для простіших випадків.

Q: Навіщо const enum якщо є звичайний enum?


A: const enum підставляє кожне звертання як літеральне значення під час компіляції. Жодного об'єкта в бандлі. Мінус: Object.values(), зворотнє відображення і будь-яка рефлексія в рантаймі стають недоступними.

Q: Числовий enum має значення 1, 5 і 10. Що поверне Enum[3]?


A: undefined. Зворотнє відображення існує тільки для тих числових значень що явно задані в enum. Пропуски в послідовності не мають записів в об'єкті. Реальна пастка якщо використовуєш числові enum для конвертації ідентифікаторів з БД в імена - отримаєш undefined без жодної помилки.

Q: Як безпечно перевірити чи значення з API є членом рядкового enum?


A: Object.values(MyEnum).includes(apiValue as MyEnum). Це перевіряє в рантаймі що вхідний рядок є одним з дозволених значень перед тим як трактувати його як типізований член enum.

Приклади

Базовий: числовий і рядковий enum поруч

typescript
enum HttpStatus { OK = 200, BadRequest = 400, NotFound = 404 } // Зворотнє відображення працює для числових enum const code = 404; console.log(HttpStatus[code]); // 'NotFound' enum OrderStatus { Pending = 'pending', Shipped = 'shipped', Delivered = 'delivered' } // Рядковий enum: зрозумілі значення, чиста серіалізація в JSON const status: OrderStatus = OrderStatus.Shipped; console.log(JSON.stringify({ status })); // {"status":"shipped"}

HttpStatus[404] повертає 'NotFound' бо числовий enum зберігає зворотні записи. OrderStatus не має зворотнього відображення, але рядкові значення серіалізуються чисто.

Середній рівень: типобезпечна обробка замовлень в Express

typescript
enum OrderStatus { Pending = 'pending', Processing = 'processing', Shipped = 'shipped', Delivered = 'delivered' } function handleOrder(status: OrderStatus): string { if (status === OrderStatus.Shipped) { return 'Відправляємо сповіщення про доставку'; } // Це не скомпілюється - TypeScript відловлює помилку до рантайму: // if (status === OrderStatus.Cancelled) { } // Error: Property 'Cancelled' does not exist return 'Дія не потрібна'; } handleOrder(OrderStatus.Shipped); // OK handleOrder('shipped'); // Error: Argument of type 'string' is not assignable to type 'OrderStatus'

Передача рядка 'shipped' не пройде навіть якщо значення збігається, бо TypeScript очікує OrderStatus, а не string. В цьому і є вся суть enum.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?