Menu

Enum em TypeScript: enums numéricos, de string e const enum

Um enum em TypeScript é um conjunto nomeado de constantes, como enum Direction { Up, Down }. Veja enums numéricos e de string, o JavaScript que um enum gera, reverse mapping, como iterar um enum, const enums e quando uma union de literais de string ou um objeto as const é a melhor escolha.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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:

enumUnion de literaisObjeto as const
Existe em tempo de execuçãosim, um objetonãosim, um objeto comum
Iterar os valoresObject.values (numérico: filtrar)não, não há o que iterarObject.values
Aceita um "red" comumnão (enums de string)simsim
Acesso por nome X.Redsimnãosim
Reverse mappingsó enums numéricosnãonão
Roda com type stripping do Nodenãosimsim
Permitido com erasableSyntaxOnlynãosimsim
Sintaxe extra para aprenderregras de enum, const enumsnenhumao 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR