Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Чому enum часто вважають антипатерном у TS?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)`enum` часто називають антипатерном, тому що це "магія часу виконання", яка генерує реальний JavaScript-код, погано працює з tree-shaking і API/JSON, і не завжди гарантує типобезпеку. **Ключове:** замість `enum` рекомендують `as const`-об'єкт або union рядкових літералів - вони не створюють зайвого коду і краще інтегруються з екосистемою JS.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Коротко: чому `enum` часто називають антипатерном > Тому що `enum` - це **"магія часу виконання"**, > яка **псує типобезпеку**, > **обтяжує код** і **гірше інтегрується з екосистемою JS**, > ніж альтернативи на кшталт `const enum` чи `as const`-об'єктів. --- ## 1. `enum` існує і в runtime, і в compile-time Головна особливість (і проблема) `enum` - він **генерує JavaScript-код** під час компіляції. Приклад: ```javascript enum Direction { Up, Down, Left, Right, } ``` Компілюється в JS як: ```javascript "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 створює двосторонню таблицю: ```javascript Direction.Up === 0 Direction[0] === "Up" ``` Це зручно, але небезпечно: ```javascript enum Role { Admin, User } const role = Role[0]; // "Admin" ``` Значення можуть бути **звернені навпаки**, що створює **приховані вразливості й плутанину**. --- ## 3. Значення enum не типобезпечні Enum поводиться **майже як об'єкт зі значеннями** `number | string`, і TypeScript **часто не захищає** від помилок присвоєння. Приклад: ```javascript enum Role { Admin, User } const role: Role = 42; // допустимо (!) ``` Це неочевидно і **ламає** сенс enum, тому що `42` - неіснуюче значення, але TS його не відфільтрує. --- ## 4. Змішування типів (numeric + string enums) Можна писати ось так: ```javascript enum Mixed { Yes = "YES", No = 0, } ``` Таке поєднання чисел і рядків призводить до **непередбачуваної поведінки** і ускладнює перевірку типів. --- ## 5. `enum` погано працює з tree-shaking Оскільки `enum` компілюється в реальний JS-об'єкт, він **не може бути видалений** оптимізатором під час збірки (наприклад, Terser чи esbuild). Приклад: ```javascript enum Colors { Red, Blue, Green } ``` Навіть якщо `Colors` не використовується, він **все одно потрапить у бандл**. А от об'єкт з `as const` - **видаляється**, якщо не використовується: ```javascript const Colors = { Red: 0, Blue: 1, Green: 2 } as const; ``` --- ## 6. Погана сумісність з plain JS / JSON / API Enum - це об'єкт часу виконання, і його значення не збігаються з простими рядками. Приклад: ```javascript enum Status { Active = "active", Inactive = "inactive" } function setStatus(status: Status) { ... } setStatus("active"); // Помилка - TS чекає Status.Active ``` Хоча рядок `"active"` **за змістом збігається**, TypeScript вимагає саме `Status.Active`. Це заважає використовувати enum з даними з API. Краще використовувати рядковий літерал: ```javascript type Status = "active" | "inactive"; ``` --- ## 7. Труднощі при серіалізації та дебазі ```javascript 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` - *змішує ці два рівні*. Приклад: ```javascript enum Fruit { Apple, Orange } function eat(fruit: Fruit) { ... } const a = Fruit.Apple; // змінна і тип в одному ``` Це "магія", яка ламає принципи передбачуваності TS. --- ## 9. Альтернатива: `as const` + `typeof` Сучасний і безпечніший спосіб - **літеральні об'єкти з** `as const`. Приклад: ```javascript 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`: ```javascript const enum Direction { Up, Down, Left, Right, } ``` TypeScript **вбудовує** значення напряму: ```javascript const move = Direction.Up; // компілюється в: const move = 0; ``` Але: - не працює з `isolatedModules: true` (наприклад, з Babel); - ламає дебаг (значення перетворюються на числа); - може бути несумісний з іншими інструментами (наприклад, SWC, ts-node). --- ## 11. `enum` ламає ідею "тип як документ контракту" Наприклад, коли ти використовуєш `enum` у публічному API, споживачі бачать не літеральні значення, а числові коди. Це робить тип менш самодокументованим. Приклад: ```javascript 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'`) | Ні | Так | Так | Так |Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.