Що таке enum у TypeScript
Enum у TypeScript - це набір іменованих констант, який компілюється в JavaScript-об'єкт. Замість "магічних" рядків і чисел розкиданих по коду ти отримуєш безпечні типи і зрозумілі імена.
Теорія
TL;DR
- Enum схожий на список контактів: телефонуєш по імені, а не по номеру. TypeScript стежить, щоб використовувались тільки дійсні імена.
- Числові enum автоматично інкрементуються (0, 1, 2...) і підтримують зворотнє відображення. Рядкові enum вимагають явних значень і зворотнього відображення не мають.
const enumпідставляє значення в код під час компіляції. Жодного об'єкта в рантаймі.- Для фіксованих наборів (статуси, ролі, напрямки) - enum. Для даних ззовні - union types.
Короткий приклад
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 повертає більше ніж числа
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 в рантаймі
const enum Status {
Active = 'active',
Inactive = 'inactive'
}
const list = Object.values(Status); // Помилка в рантаймі: Status не існуєconst enum не має об'єкта в рантаймі. Якщо потрібна ітерація, використовуй звичайний enum.
Помилка 3: Числові та рядкові значення в одному enum
// Погано: зворотнє відображення ламається для числових членів
enum Mixed {
Success = 1,
Error = 'error'
}Вибирай один тип. Змішані enum ламають зворотнє відображення і важко читаються.
Помилка 4: Enum для даних з зовнішнього API
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 поруч
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
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.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.