Un enum en TypeScript es un conjunto de constantes con nombre. enum Direction { Up, Down, Left, Right } crea a la vez un tipo, Direction, y un objeto en tiempo de ejecución a cuyos miembros accedes como Direction.Up. Los miembros se numeran desde 0 salvo que les des valores, y los enums de string dan a cada miembro un string legible.
Los enums son una de las pocas características de TypeScript que no son solo tipos: un enum se convierte en un objeto real de JavaScript cuando se compila el código.
Enums numéricos
Sin inicializadores, los miembros reciben 0, 1, 2 y así sucesivamente. Da un número al primer miembro y los demás continúan a partir de él. También puedes fijar cada valor de forma explícita, que es la opción segura cuando los números se guardan en una base de datos o se envían por la red.
Confiar en la numeración automática está bien para valores que nunca salen del programa. Si el orden de los miembros puede cambiar y los números se guardan en algún sitio, insertar un miembro en medio renumera sin avisar todo lo que va detrás.
A qué compila un enum
Los tipos se borran, pero un enum no. Este es el JavaScript que genera TypeScript para un enum numérico y uno de strings:
enum Direction { Up, Down, Left, Right }
enum Status { Active = "ACTIVE", Inactive = "INACTIVE" }
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 = {}));
var Status;
(function (Status) {
Status["Active"] = "ACTIVE";
Status["Inactive"] = "INACTIVE";
})(Status || (Status = {}));
Direction["Up"] = 0 devuelve 0, así que Direction[0] = "Up" se asigna en la misma sentencia. Por eso un enum numérico funciona en los dos sentidos: de nombre a número y de número a nombre. Eso es el mapeo inverso (reverse mapping). Los enums de string solo van de nombre a valor.
El objeto Direction impreso tiene ocho claves: los cuatro nombres y los cuatro números. Eso importa en cuanto lo recorres.
Enums de string
Cada miembro de un enum de strings necesita un valor de string explícito. Los valores aparecen tal cual en los logs, el JSON y las bases de datos, lo que hace que los enums de string sean más fáciles de depurar que los números.
Un enum de strings es nominal de una manera que sorprende: un string normal no se le puede asignar, aunque el texto coincida con el valor de un miembro.
index.ts(7,5): error TS2820: Type '"ACTIVE"' is not assignable to type 'Status'. Did you mean 'Status.Inactive'?
(La sugerencia del mensaje es una suposición del compilador y aquí se equivoca; la solución es Status.Active). En la otra dirección, un valor Status se puede usar en cualquier sitio donde se espere un string. Cuando los valores llegan como strings, desde JSON o un formulario, conviértelos con una comprobación como la de la sección sobre comprobar valores, más abajo.
Usar un enum como tipo
El nombre del enum es un tipo cuyos valores son sus miembros. Junto con switch, TypeScript comprueba que se maneja cada miembro cuando la función debe devolver un valor:
Si se añade un miembro nuevo a Shape sin un case nuevo, sides deja de compilar con TS2366, Function lacks ending return statement and return type does not include 'undefined'. La página de switch muestra la comprobación exhaustiva más estricta basada en never.
Las últimas líneas muestran un punto débil real de los enums numéricos. Un literal numérico que no coincide con ningún miembro, const level: Level = 99, es un error de compilación (TS2322), pero se acepta cualquier valor de tipo number, así que 57 pasa. Los enums de string no tienen este agujero.
Recorrer un enum
Un enum es un objeto en tiempo de ejecución, así que Object.keys, Object.values y Object.entries funcionan. En un enum de strings devuelven exactamente los miembros. En un enum numérico devuelven también las entradas del mapeo inverso, que tienes que filtrar:
Para tipar una variable como «uno de los nombres de miembro del enum», usa keyof typeof Direction, que es la unión "Up" | "Down" | "Left" | "Right". Así Direction[name] busca el valor con total seguridad de tipos.
Un enum de strings no tiene mapeo inverso, así que para obtener el nombre de un miembro a partir de su valor, busca en las entradas: Object.entries(Status).find(([, v]) => v === "ACTIVE")?.[0] es "Active", o undefined cuando ningún miembro tiene ese valor.
Comprobar si un valor está en un enum
Los datos que vienen de fuera del programa son un string o un number normal. Un type guard los comprueba contra los valores del enum y los estrecha al tipo del enum:
Evita raw as Status con entradas no fiables: la aserción compila, pero no se comprueba nada en tiempo de ejecución, así que "DELETED" recorrería el programa tipado como un Status válido.
const enum
const enum le pide al compilador que elimine el enum y escriba el valor de cada miembro allí donde se usa. No hay ningún objeto en tiempo de ejecución, así que no se puede recorrer ni usar el mapeo inverso.
const enum ahorra unos bytes y una consulta de propiedad, pero depende de que el compilador vea la declaración del enum al compilar cada archivo que lo usa. Las herramientas que transpilan archivo por archivo, como Babel y swc, no pueden ver un const enum declarado en otro archivo; el type stripping de Node rechaza los const enum igual que cualquier otro enum; y con isolatedModules o verbatimModuleSyntax TypeScript da el error TS2748 cuando usas un const enum de un archivo de declaraciones. La mayoría del código de aplicación no necesita const enums.
Enum frente a tipo unión y objeto as const
Hay tres formas habituales de definir un conjunto fijo de valores:
enum | Unión de literales | Objeto as const | |
|---|---|---|---|
| Existe en tiempo de ejecución | sí, un objeto | no | sí, un objeto normal |
| Recorrer los valores | Object.values (numérico: filtrar) | no, no hay nada que recorrer | Object.values |
Acepta un "red" normal | no (enums de string) | sí | sí |
Acceso por nombre X.Red | sí | no | sí |
| Mapeo inverso | solo enums numéricos | no | no |
| Funciona con el type stripping de Node | no | sí | sí |
Permitido con erasableSyntaxOnly | no | sí | sí |
| Sintaxis extra que aprender | reglas de enum, const enums | ninguna | el patrón con typeof |
Muchos equipos usan ahora por defecto una unión de literales de string, y pasan al objeto as const cuando necesitan los valores en tiempo de ejecución (para recorrerlos o construir un desplegable). Las razones: las uniones son tipos puros y desaparecen de la salida; aceptan los strings normales que entregan el JSON y las APIs; y los enums son la única pieza del TypeScript de todos los días que no es «JavaScript más tipos borrables».
Ese último punto ya tiene consecuencias prácticas. Node ejecuta archivos .ts directamente eliminando los tipos, y un enum no es algo que pueda eliminar:
node status.ts
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode
La opción --experimental-transform-types de Node hace que los enums funcionen, y la opción del compilador erasableSyntaxOnly marca cada enum con el error TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., así que un proyecto puede prohibirlos desde el principio. Consulta ejecutar TypeScript para ver cómo funciona el type stripping. Nada de esto hace que los enums estén mal: el código compilado con tsc o con un bundler los ejecuta sin problema, y un proyecto que ya usa enums gana poco convirtiéndolos.
Preguntas frecuentes
¿Qué es un enum en TypeScript?
Un conjunto de constantes con nombre que es a la vez un tipo y un objeto en tiempo de ejecución: enum Direction { Up, Down } te permite escribir Direction.Up y usar Direction como tipo de un parámetro. A diferencia de la mayoría de características de TypeScript, un enum no se borra: compila a un objeto de JavaScript que existe en tiempo de ejecución.
¿Cómo recorro un enum en TypeScript?
En un enum de strings, Object.values(MyEnum) da los valores y Object.keys(MyEnum) los nombres. Un enum numérico también contiene entradas del mapeo inverso ("0": "Up"), así que fíltralas: Object.keys(Direction).filter((k) => isNaN(Number(k))) da solo los nombres. Un const enum no se puede recorrer, porque no existe en tiempo de ejecución.
¿Cómo convierto un string en un valor de enum en TypeScript?
Comprueba el string contra los valores del enum en un type guard: function isStatus(s: string): s is Status { return (Object.values(Status) as string[]).includes(s); }. Tras la comprobación, s tiene tipo Status. Un simple s as Status compila pero no comprueba nada en tiempo de ejecución.
¿Uso un enum o un tipo unión en TypeScript?
Muchos equipos prefieren una unión de literales de string (type Status = "active" | "inactive"), o un objeto as const cuando también necesitan los valores en tiempo de ejecución. Las uniones se borran por completo, funcionan con el type stripping integrado de Node y con la opción erasableSyntaxOnly, y aceptan strings normales como "active". Los enums también están bien, sobre todo en código que ya los usa.
¿Qué diferencia hay entre enum y const enum?
Un enum normal compila a un objeto que puedes recorrer y consultar en tiempo de ejecución. Un const enum se elimina al compilar y cada uso se sustituye por su valor (Size.Large pasa a ser 2), así que no cuesta nada en tiempo de ejecución, pero no se puede recorrer y las herramientas que compilan archivo por archivo lo restringen.