Чому enum часто вважають антипатерном у TS?
Коротко: чому enum часто називають антипатерном
Тому що
enum- це "магія часу виконання", яка псує типобезпеку, обтяжує код і гірше інтегрується з екосистемою JS, ніж альтернативи на кшталтconst enumчиas const-об'єктів.
1. enum існує і в runtime, і в compile-time
Головна особливість (і проблема) enum -
він генерує JavaScript-код під час компіляції.
Приклад:
enum Direction {
Up,
Down,
Left,
Right,
}Компілюється в JS як:
"use strict";
var Direction;
(function (Direction) {
Direction[(Direction["Up"] = 0)] = "Up";
Direction[(Direction["Down"] = 1)] = "Down";
Direction[(Direction["Left"] = 2)] = "Left";
Direction[(Direction["Right"] = 3)] = "Right";
})(Direction || (Direction = {}));Тобто enum - не суто типова конструкція,
а генерує реальний об'єкт, що:
- додає зайвий код у бандл,
- може поводитися не так, як очікуєш.
2. Двостороннє відображення (reverse mapping) = потенційний баг
TypeScript створює двосторонню таблицю:
Direction.Up === 0
Direction[0] === "Up"Це зручно, але небезпечно:
enum Role { Admin, User }
const role = Role[0]; // "Admin"Значення можуть бути звернені навпаки, що створює приховані вразливості й плутанину.
3. Значення enum не типобезпечні
Enum поводиться майже як об'єкт зі значеннями number | string,
і TypeScript часто не захищає від помилок присвоєння.
Приклад:
enum Role { Admin, User }
const role: Role = 42; // допустимо (!)Це неочевидно і ламає сенс enum,
тому що 42 - неіснуюче значення,
але TS його не відфільтрує.
4. Змішування типів (numeric + string enums)
Можна писати ось так:
enum Mixed {
Yes = "YES",
No = 0,
}Таке поєднання чисел і рядків призводить до непередбачуваної поведінки і ускладнює перевірку типів.
5. enum погано працює з tree-shaking
Оскільки enum компілюється в реальний JS-об'єкт,
він не може бути видалений оптимізатором під час збірки (наприклад, Terser чи esbuild).
Приклад:
enum Colors { Red, Blue, Green }Навіть якщо Colors не використовується,
він все одно потрапить у бандл.
А от об'єкт з as const - видаляється,
якщо не використовується:
const Colors = { Red: 0, Blue: 1, Green: 2 } as const;6. Погана сумісність з plain JS / JSON / API
Enum - це об'єкт часу виконання, і його значення не збігаються з простими рядками.
Приклад:
enum Status { Active = "active", Inactive = "inactive" }
function setStatus(status: Status) { ... }
setStatus("active"); // Помилка - TS чекає Status.ActiveХоча рядок "active" за змістом збігається,
TypeScript вимагає саме Status.Active.
Це заважає використовувати enum з даними з API.
Краще використовувати рядковий літерал:
type Status = "active" | "inactive";7. Труднощі при серіалізації та дебазі
enum Status { Active, Inactive }
JSON.stringify(Status);
// {"0":"Active","1":"Inactive","Active":0,"Inactive":1}Замість простого переліку - марна мішанина, з якої важко дістати потрібні значення.
8. Поведінка enum ламає концепцію "value-level vs type-level"
TypeScript зазвичай розділяє:
- type level (лише під час компіляції)
- value level (у runtime)
enum - змішує ці два рівні.
Приклад:
enum Fruit { Apple, Orange }
function eat(fruit: Fruit) { ... }
const a = Fruit.Apple; // змінна і тип в одномуЦе "магія", яка ламає принципи передбачуваності TS.
9. Альтернатива: as const + typeof
Сучасний і безпечніший спосіб - літеральні об'єкти з as const.
Приклад:
const Direction = {
Up: "up",
Down: "down",
Left: "left",
Right: "right",
} as const;
type Direction = typeof Direction[keyof typeof Direction];Переваги:
- Не створює JS-коду (лише типи);
- Працює з API та JSON;
- Дружній до tree-shaking;
- Не ламає типізацію.
10. const enum - компроміс (але теж з нюансами)
Якщо все ж потрібен enum, краще використовувати const enum:
const enum Direction {
Up,
Down,
Left,
Right,
}TypeScript вбудовує значення напряму:
const move = Direction.Up;
// компілюється в:
const move = 0;Але:
- не працює з
isolatedModules: true(наприклад, з Babel); - ламає дебаг (значення перетворюються на числа);
- може бути несумісний з іншими інструментами (наприклад, SWC, ts-node).
11. enum ламає ідею "тип як документ контракту"
Наприклад, коли ти використовуєш enum у публічному API,
споживачі бачать не літеральні значення, а числові коди.
Це робить тип менш самодокументованим.
Приклад:
enum Status { Success, Fail }
function getStatus(): Status { ... }Повертає 0 | 1, але IDE не підкаже "Success" | "Fail" -
розуміння коду ускладнюється.
12. Підсумок: коли enum дійсно потрібен
enum виправданий лише в рідкісних випадках:
- при сумісності з наявним C#/Java кодом;
- при використанні із зовнішніми бібліотеками, де enum - частина API;
- у специфічних compile-time сценаріях (
const enum).
Для всього іншого:
використовуй string literal union або as const об'єкт.
Фінальна порівняльна таблиця
| Підхід | Генерує код? | Безпечний? | Зручний з API? | Tree-shaking | Рекомендується |
|---|---|---|---|---|---|
enum | Так | Частково | Ні | Ні | Ні |
const enum | Ні (inline) | Так | Частково | Так | Обережно |
as const об'єкт | Ні | Так | Так | Так | Рекомендується |
| Union типів (`'a' | 'b'`) | Ні | Так | Так | Так |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.