const enum vs enum

Главное отличие: обычный enum компилируется в JS-объект (есть в рантайме), const enum инлайнится в значения (нет в рантайме).

Быстрая сравнительная таблица

Аспект enum const enum
Runtime-объект ✅ есть ❌ нет (инлайн)
Размер бандла + ~50-200 байт на enum 0
Tree-shaking ⚠️ работает плохо ✅ работает идеально
Reverse mapping (Color[0]"Red") ✅ для числовых ❌ нет
Object.keys(Enum) / Object.values(Enum) ✅ работает ❌ нет
Динамическое использование ✅ работает ❌ нет
isolatedModules: true ✅ работает ошибка (Babel, esbuild, swc)
Подходит для библиотек ⚠️ заставляет потребителя тащить enum ❌ нельзя в .d.ts для внешних потребителей

Минимальный пример

// Обычный enum
enum Color { Red, Green, Blue }
const c = Color.Red;
// JS: var Color; (function(Color){...})(Color||(Color={})); const c = Color.Red;

// const enum
const enum Color { Red, Green, Blue }
const c = Color.Red;
// JS: const c = 0;  ← полностью инлайн, объекта Color нет

Когда использовать const enum

  • Чистое приложение (не библиотека)
  • Сборщик: tsc (не Babel/esbuild) или isolatedModules: false
  • Не нужен runtime-доступ (итерация, reverse mapping)
  • Хочешь нулевой overhead

Когда использовать обычный enum

  • Библиотека для внешних потребителей
  • Нужен Object.values(Enum) для итерации
  • Используется в switch/Map в рантайме
  • Сборщик с isolatedModules: true (Vite, Next.js, Remix, esbuild, swc)

Когда НЕ использовать ни тот ни другой

→ Используй as const объект или union type:

// 1. as const объект — runtime + типы
const Color = { Red: 'red', Green: 'green', Blue: 'blue' } as const;
type Color = (typeof Color)[keyof typeof Color];  // 'red' | 'green' | 'blue'

// 2. Чистый union — нет runtime
type Color = 'red' | 'green' | 'blue';

Преимущества:

  • ✅ Идеальный tree-shaking
  • ✅ Работает везде (Vite, esbuild, любой bundler)
  • ✅ Можно итерировать (Object.values(Color))
  • ✅ Сериализуется в JSON естественно

Главные подводные камни

1. isolatedModules: true + const enum = 💥

Vite, Next.js, esbuild, swc, Babel компилируют файлы изолированно — не видят соседних файлов. const enum требует знания всех значений на этапе компиляции — поэтому не работает.

Симптом: Error: Cannot access ambient const enums when 'isolatedModules' is enabled.

Решение: убрать const, использовать обычный enum или union.

2. Числовой enum принимает любое число

enum Status { Active, Inactive }  // 0, 1
function set(s: Status) { /* ... */ }
set(999);  // ✅ компилируется! Бага в типизации

Решение: используй строковый enum или union — там такого нет.

3. Обычный enum тяжелее tree-shake

Webpack/Rollup видят enum как объект с побочными эффектами и могут оставить его в бандле, даже если используется один-два значения.

Решение: const enum или as const объект.

4. const enum нельзя экспортировать в .d.ts для рантайм-потребителей

Если ты пишешь библиотеку и экспортишь const enum — потребитель НЕ сможет использовать его в рантайме (его просто нет).

5. Гетерогенные enum (числа + строки) — anti-pattern

enum Mixed { No = 0, Yes = "YES" }  // ❌ не делай так

Сбивает с толку, ломает reverse mapping.

Рекомендация по умолчанию

Нужны типы для конечного набора значений?
├── Используется в рантайме (итерация, ключи)?
│   ├── Да → `as const` объект
│   └── Нет → union type ("a" | "b" | "c")
└── Нужно совместить с библиотечным API, требующим объект?
    ├── isolatedModules: true (Vite/Next/esbuild)? → обычный enum (строковый)
    └── isolatedModules: false (tsc)? → const enum

Связанные темы

Ресурсы