Enums en TypeScript: const enum vs enum y cuándo evitarlos

Los enums de TypeScript son uno de los pocos añadidos al lenguaje que generan código JavaScript real, no solo tipos. Esta característica tiene consecuencias que conviene entender antes de usarlos: código adicional en el bundle, comportamiento sorprendente del reverse mapping y problemas con herramientas modernas de compilación. Aquí está todo lo que debes saber para decidir cuándo usarlos y cuándo no.

Numeric enums y el reverse mapping

El enum numérico básico asigna valores enteros a cada miembro empezando desde 0. Lo que mucha gente no sabe es que TypeScript genera también el mapeo inverso: puedes obtener el nombre a partir del número:

enum Direccion {
  Norte, // 0
  Sur,   // 1
  Este,  // 2
  Oeste, // 3
}

console.log(Direccion.Norte); // 0
console.log(Direccion[0]);    // "Norte" ? reverse mapping

El JavaScript generado deja claro por qué esto consume más código del esperado:

var Direccion;
(function (Direccion) {
  Direccion[Direccion["Norte"] = 0] = "Norte";
  Direccion[Direccion["Sur"]   = 1] = "Sur";
  Direccion[Direccion["Este"]  = 2] = "Este";
  Direccion[Direccion["Oeste"] = 3] = "Oeste";
})(Direccion || (Direccion = {}));

String enums: sin reverse mapping pero más legibles

Los string enums asignan cadenas explícitas y no generan reverse mapping. Son más seguros porque evitan la comparación con números mágicos:

enum Estado {
  Cargando  = "CARGANDO",
  Exito     = "EXITO",
  Error     = "ERROR",
}

function manejar(e: Estado): void {
  if (e === Estado.Exito) {
    console.log("Todo bien");
  }
}

const enum: inlining en tiempo de compilación

const enum le dice al compilador que sustituya cada uso del enum por el valor literal directamente, eliminando el objeto del bundle. El resultado es código más pequeño:

const enum Tecla {
  Enter  = 13,
  Escape = 27,
  Espacio = 32,
}

function onKeyDown(codigo: number): void {
  if (codigo === Tecla.Enter) procesar();
}
// Compilado a: if (codigo === 13) procesar();

El problema de const enum aparece con isolatedModules: true, que usan Vite, esbuild, swc y Babel. Estas herramientas transpilan fichero a fichero sin ver el árbol completo, así que no pueden hacer el inlining. TypeScript lanzará un error si usas const enum con esta opción activa.

Por qué el tree-shaking falla con enums

El patrón IIFE que genera TypeScript para los enums no es analizable estáticamente por los bundlers. Webpack, Rollup y esbuild no pueden determinar qué miembros del enum se usan realmente, así que incluyen el objeto completo aunque solo uses un valor. Con librerías grandes, esto puede aumentar el bundle de forma innecesaria.

Alternativas: as const y union types de literales

La alternativa idiomática moderna en TypeScript es usar as const sobre un objeto para obtener tipos literales, o directamente un union type de strings:

// as const: objeto con tipos literales, tree-shakeable
const ESTADO = {
  Cargando : "CARGANDO",
  Exito    : "EXITO",
  Error    : "ERROR",
} as const;

type Estado = typeof ESTADO[keyof typeof ESTADO];
// "CARGANDO" | "EXITO" | "ERROR"

// Union type directo: la opción más simple
type Direccion = "Norte" | "Sur" | "Este" | "Oeste";

Las ventajas de as const frente a los enums: genera código JavaScript ordinario (un objeto literal), es compatible con cualquier transpilador, el tree-shaking funciona y los valores son strings legibles en los logs y en la red en lugar de números opacos.

Cuándo sí tiene sentido usar enums

Los enums siguen siendo útiles en proyectos que usan el compilador de TypeScript directamente (sin Babel ni esbuild), que necesitan el reverse mapping de los numeric enums, o que trabajan con código legacy que ya los usa ampliamente. Si estás empezando un proyecto nuevo con herramientas modernas, la combinación de as const y union types cubre el 99% de los casos con menos código generado y mejor compatibilidad con el ecosistema.

Imagen: Pexels / Mathews Jumba

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP