Um enum em TypeScript é um conjunto nomeado de constantes. enum Direction { Up, Down, Left, Right } cria ao mesmo tempo um tipo, Direction, e um objeto em tempo de execução cujos membros você acessa como Direction.Up. Os membros são numerados a partir de 0, a menos que você dê valores a eles, e enums de string dão a cada membro uma string legível.
Enums são um dos poucos recursos do TypeScript que não são só tipos: um enum vira um objeto JavaScript de verdade quando o código é compilado.
Enums numéricos
Sem inicializadores, os membros recebem 0, 1, 2 e assim por diante. Dê um número ao primeiro membro e os demais continuam a partir dele. Você também pode definir todos os valores explicitamente, que é a escolha segura quando os números são gravados em um banco de dados ou enviados pela rede.
Confiar na numeração automática é aceitável para valores que nunca saem do programa. Se a ordem dos membros puder mudar e os números forem persistidos em algum lugar, inserir um membro no meio renumera silenciosamente tudo o que vem depois.
O que um enum gera na compilação
Tipos são apagados, mas um enum não. Este é o JavaScript que o TypeScript gera para um enum numérico e um de string:
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 retorna 0, então Direction[0] = "Up" é definido na mesma instrução. Por isso um enum numérico mapeia nos dois sentidos: do nome para o número e do número de volta para o nome. Esse é o reverse mapping. Enums de string só mapeiam nomes para valores.
O objeto Direction impresso tem oito chaves: os quatro nomes e os quatro números. Isso importa assim que você for iterar sobre ele.
Enums de string
Cada membro de um enum de string precisa de um valor string explícito. Os valores aparecem como estão em logs, JSON e bancos de dados, o que torna enums de string mais fáceis de depurar do que números.
Um enum de string é nominal de um jeito que surpreende: uma string comum não pode ser atribuída a ele, mesmo quando o texto é igual ao valor de um membro.
index.ts(7,5): error TS2820: Type '"ACTIVE"' is not assignable to type 'Status'. Did you mean 'Status.Inactive'?
(A sugestão da mensagem é um palpite do compilador e aqui está errada; a correção é Status.Active.) No sentido contrário, um valor Status pode ser usado em qualquer lugar onde se espera uma string. Quando os valores chegam como strings, de um JSON ou de um formulário, converta-os com uma verificação como a da seção sobre verificação de valores mais abaixo.
Usando um enum como tipo
O nome do enum é um tipo cujos valores são seus membros. Junto com switch, o TypeScript verifica se todos os membros são tratados quando a função precisa retornar um valor:
Se um novo membro for adicionado a Shape sem um novo case, sides deixa de compilar com TS2366, Function lacks ending return statement and return type does not include 'undefined'. A página de switch mostra a verificação exaustiva mais rígida baseada em never.
As últimas linhas mostram uma fraqueza real dos enums numéricos. Um literal numérico que não corresponde a nenhum membro, const level: Level = 99, é erro de compilação (TS2322), mas qualquer valor do tipo number é aceito, então 57 passa. Enums de string não têm essa brecha.
Iterando sobre um enum
Um enum é um objeto em tempo de execução, então Object.keys, Object.values e Object.entries funcionam. Em um enum de string eles retornam exatamente os membros. Em um enum numérico retornam também as entradas do reverse mapping, que você filtra:
Para tipar uma variável como "um dos nomes de membro do enum", use keyof typeof Direction, que é a union "Up" | "Down" | "Left" | "Right". Aí Direction[name] busca o valor com segurança de tipos completa.
Um enum de string não tem reverse mapping, então para obter o nome de um membro a partir do valor, procure nas entradas: Object.entries(Status).find(([, v]) => v === "ACTIVE")?.[0] é "Active", ou undefined quando nenhum membro tem aquele valor.
Verificando se um valor está em um enum
Dados de fora do programa são uma string ou um number comum. Um type guard os compara com os valores do enum e os estreita para o tipo do enum:
Evite raw as Status em entradas não confiáveis: a assertion compila, mas nada é verificado em tempo de execução, então "DELETED" circularia pelo programa tipado como um Status válido.
const enums
const enum pede ao compilador para apagar o enum e escrever o valor de cada membro onde ele é usado. Não existe objeto em tempo de execução, então nada pode ser iterado nem ter reverse mapping.
const enum economiza alguns bytes e uma busca de propriedade, mas depende de o compilador ver a declaração do enum ao compilar cada arquivo que o usa. Ferramentas que transpilam um arquivo por vez, como Babel e swc, não enxergam um const enum declarado em outro arquivo; o type stripping do Node rejeita const enums assim como qualquer outro enum; e com isolatedModules ou verbatimModuleSyntax o TypeScript aponta o erro TS2748 quando você usa um const enum de um arquivo de declaração. A maior parte do código de aplicação não precisa de const enums.
Enum vs union type vs objeto as const
Há três formas comuns de definir um conjunto fixo de valores:
enum | Union de literais | Objeto as const | |
|---|---|---|---|
| Existe em tempo de execução | sim, um objeto | não | sim, um objeto comum |
| Iterar os valores | Object.values (numérico: filtrar) | não, não há o que iterar | Object.values |
Aceita um "red" comum | não (enums de string) | sim | sim |
Acesso por nome X.Red | sim | não | sim |
| Reverse mapping | só enums numéricos | não | não |
| Roda com type stripping do Node | não | sim | sim |
Permitido com erasableSyntaxOnly | não | sim | sim |
| Sintaxe extra para aprender | regras de enum, const enums | nenhuma | o padrão com typeof |
Muitos times hoje usam por padrão uma union de literais de string e passam para o objeto as const quando precisam dos valores em tempo de execução (para iterá-los ou montar um dropdown). Os motivos: unions são tipos puros e somem da saída; aceitam as strings comuns que JSON e APIs entregam; e enums são a única parte do TypeScript do dia a dia que não é "JavaScript mais tipos apagáveis".
Esse último ponto passou a ter efeito prático. O Node roda arquivos .ts diretamente removendo os tipos, e um enum não é algo que ele consiga remover:
node status.ts
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode
A flag --experimental-transform-types do Node faz enums rodarem, e a opção de compilador erasableSyntaxOnly aponta todo enum como o erro TS1294, This syntax is not allowed when 'erasableSyntaxOnly' is enabled., para que um projeto possa proibi-los desde o início. Veja executando TypeScript para entender como o type stripping funciona. Nada disso torna enums errados: código compilado com tsc ou com um bundler os executa sem problema, e um projeto que já usa enums ganha pouco convertendo.
Perguntas frequentes
O que é um enum no TypeScript?
Um conjunto nomeado de constantes que é ao mesmo tempo um tipo e um objeto em tempo de execução: enum Direction { Up, Down } permite escrever Direction.Up e usar Direction como tipo de parâmetro. Ao contrário da maioria dos recursos do TypeScript, um enum não é apagado: ele vira um objeto JavaScript que existe em tempo de execução.
Como iterar sobre um enum no TypeScript?
Em um enum de string, Object.values(MyEnum) dá os valores e Object.keys(MyEnum) os nomes. Um enum numérico também contém as entradas do reverse mapping ("0": "Up"), então filtre-as: Object.keys(Direction).filter((k) => isNaN(Number(k))) dá só os nomes. Um const enum não pode ser iterado, porque não existe em tempo de execução.
Como converter uma string em um valor de enum no TypeScript?
Verifique a string contra os valores do enum em um type guard: function isStatus(s: string): s is Status { return (Object.values(Status) as string[]).includes(s); }. Depois da verificação, s tem o tipo Status. Um simples s as Status compila, mas não verifica nada em tempo de execução.
Devo usar enum ou union type no TypeScript?
Muitos times preferem uma union de literais de string (type Status = "active" | "inactive"), ou um objeto as const quando também precisam dos valores em tempo de execução. Unions são apagadas por completo, funcionam com o type stripping nativo do Node e com a opção erasableSyntaxOnly e aceitam strings comuns como "active". Enums também servem, principalmente em projetos que já os usam.
Qual é a diferença entre enum e const enum?
Um enum comum vira um objeto que você pode iterar e consultar em tempo de execução. Um const enum é removido na compilação e cada uso é substituído pelo seu valor (Size.Large vira 2), então não custa nada em tempo de execução, mas não pode ser iterado, e ferramentas que compilam um arquivo por vez restringem seu uso.