Skip to main content

Enum vs as const | Typescript

  • Enum vs as const | Typescript

Enum vs as const no TypeScript

No ecossistema TypeScript atual, é comum preferir objetos as const + união de literais no lugar de enum, principalmente quando o objetivo é ter valores previsíveis em runtime, boa integração com o sistema de tipos e menos código emitido no JavaScript. Isso acontece porque enum é uma das poucas features do TypeScript que não existem apenas no nível de tipos: ele também gera estrutura em runtime. (TypeScript)

A ideia central

Considere este padrão:

const Status = {
PENDING: 'pending',
APPROVED: 'approved',
REJECTED: 'rejected',
} as const;

type Status = typeof Status[keyof typeof Status];

Aqui você obtém duas coisas ao mesmo tempo:

  1. um objeto real em runtime
  2. um tipo derivado dos valores do objeto

O as const faz com que o TypeScript preserve os valores literais exatos, em vez de alargá-los para string. A documentação oficial descreve isso como uma forma de manter a expressão no tipo mais específico possível e inclusive cita que esse recurso permite criar padrões “parecidos com enum” sem usar enum. (TypeScript)

O que esse código produz em termos de tipo

O tipo abaixo:

type Status = typeof Status[keyof typeof Status];

resulta em:

type Status = 'pending' | 'approved' | 'rejected';

Isso acontece porque:

  • typeof Status pega o tipo do objeto
  • keyof typeof Status pega as chaves do objeto
  • typeof Status[keyof typeof Status] pega a união dos valores dessas chaves

Esse modelo se encaixa naturalmente com o sistema de literal types e com os recursos modernos de manipulação de tipos do TypeScript. (TypeScript)

Por que muita gente evita enum

1. enum gera código em runtime

Exemplo:

enum Status {
Pending = 'pending',
Approved = 'approved',
Rejected = 'rejected',
}

Isso não é apenas um tipo. O compilador gera JavaScript correspondente. Já uma união literal pura, como:

type Status = 'pending' | 'approved' | 'rejected';

não gera nada em runtime. A documentação oficial destaca explicitamente que enums são uma das poucas extensões do TypeScript que não operam apenas no nível de tipos. (TypeScript)

2. Numeric enums podem introduzir confusão

Por padrão, enums podem ser numéricos:

enum Status {
Pending,
Approved,
Rejected,
}

Nesse caso, os valores são 0, 1 e 2. A documentação oficial mostra esse comportamento como padrão. Isso pode causar código menos semântico, comparações pouco legíveis e bugs sutis quando números “vazam” para lógica de negócio, logs ou serialização. (TypeScript)

3. String literal unions compõem melhor com o sistema de tipos

Literal types, unions, keyof, typeof, indexed access e template literal types formam hoje a base da modelagem avançada em TypeScript. Esse conjunto costuma se integrar melhor entre si do que enum, principalmente quando você quer derivar tipos, combinar valores e manter inferência forte. A documentação moderna do TS trata esses recursos como blocos centrais do sistema de tipos. (TypeScript)

Comparando as abordagens

Usando enum

enum Status {
Pending = 'pending',
Approved = 'approved',
Rejected = 'rejected',
}

function updateStatus(status: Status) {
console.log(status);
}

updateStatus(Status.Pending);

Usando as const

const Status = {
Pending: 'pending',
Approved: 'approved',
Rejected: 'rejected',
} as const;

type Status = (typeof Status)[keyof typeof Status];

function updateStatus(status: Status) {
console.log(status);
}

updateStatus(Status.Pending);

Do ponto de vista de uso, os dois ficam parecidos. A diferença está em como o TypeScript modela isso e no que é emitido em JavaScript. No padrão com as const, você continua tendo autocomplete, restrição dos valores permitidos e integração natural com unions literais. (TypeScript)

Vantagens do padrão com as const

Menos surpresa em runtime

Os valores continuam sendo exatamente strings conhecidas:

'pending'
'approved'
'rejected'

Isso costuma ser melhor para:

  • logs
  • payloads JSON
  • integração com APIs
  • persistência em banco
  • comparação explícita no código

Literal types existem justamente para restringir um valor a um conjunto exato de literais, em vez de permitir qualquer string. (TypeScript)

Melhor composição com outros tipos

Exemplo com template literal types:

const Status = {
Pending: 'pending',
Approved: 'approved',
Rejected: 'rejected',
} as const;

type Status = (typeof Status)[keyof typeof Status];
type StatusEvent = `status:${Status}`;

Nesse caso, StatusEvent vira:

type StatusEvent =
| 'status:pending'
| 'status:approved'
| 'status:rejected';

Essa forma de composição é muito natural quando se usa união de literais. Template literal types existem justamente para expandir strings a partir de unions concretas. (TypeScript)

Bom equilíbrio entre runtime e type system

Você tem um objeto real para usar no código:

Status.Pending

e, ao mesmo tempo, um tipo derivado automaticamente. Isso evita duplicação manual entre “lista de valores” e “tipo aceito”. A própria release note do TypeScript 3.4 mostra const assertions como base para padrões parecidos com enum em JavaScript/TypeScript. (TypeScript)

Limitações e cuidados desse padrão

O objeto ainda existe em runtime

Embora seja mais simples que enum, esse objeto continua existindo em runtime:

const Status = { ... } as const;

Ou seja, se você só precisa do tipo e nunca usa os valores em execução, pode simplificar ainda mais:

type Status = 'pending' | 'approved' | 'rejected';

Nesse caso, não existe objeto algum no JavaScript final. Isso decorre da própria natureza de types/unions no TS: eles são apagados na compilação. Já enums permanecem como estrutura de runtime. (TypeScript)

enum ainda é válido

Nada na documentação oficial diz que enum está proibido ou obsoleto. Pelo contrário: o recurso continua oficialmente documentado e suportado. A questão é mais de ergonomia, composição com o type system e previsibilidade do código gerado. (TypeScript)

Quando usar cada abordagem

Use união literal pura quando

Você só precisa do tipo:

type Status = 'pending' | 'approved' | 'rejected';

Boa escolha para DTOs, contratos simples e assinaturas de função. Literal types foram feitos exatamente para representar conjuntos exatos de valores válidos. (TypeScript)

Use objeto as const quando

Você quer:

  • valores reais em runtime
  • autocomplete com namespace simples
  • derivar o tipo a partir do próprio objeto
  • evitar enum

Exemplo:

export const Status = {
Pending: 'pending',
Approved: 'approved',
Rejected: 'rejected',
} as const;

export type Status = (typeof Status)[keyof typeof Status];

Esse padrão é hoje um dos mais usados em código TypeScript moderno. Ele é compatível com o modelo de const assertions descrito oficialmente pela linguagem. (TypeScript)

Use enum quando

Você realmente quer a semântica explícita de enumeração ou está em um código onde enum já é padrão do time:

enum LogLevel {
Error = 'error',
Warn = 'warn',
Info = 'info',
}

Funciona e continua sendo recurso oficial. Só não costuma ser a opção mais flexível para composição avançada de tipos. (TypeScript)

Exemplo prático recomendado

export const Status = {
Pending: 'pending',
Approved: 'approved',
Rejected: 'rejected',
} as const;

export type Status = (typeof Status)[keyof typeof Status];

export interface Order {
id: string;
status: Status;
}

export function canCancelOrder(order: Order): boolean {
return order.status === Status.Pending;
}

O que isso entrega

  • tipo restrito aos valores válidos
  • objeto centralizado com os valores aceitos
  • comparação explícita
  • integração simples com APIs e JSON
  • ausência de numeric enum por trás da modelagem

Esse formato aproveita diretamente literal types e const assertions, dois recursos centrais da modelagem moderna em TypeScript. (TypeScript)

Regra prática

Uma regra simples e útil para projeto moderno em TypeScript é:

  • se precisa só do tipo, use união literal
  • se precisa do tipo e dos valores em runtime, use objeto as const
  • use enum apenas quando houver motivo claro para isso

Referências oficiais

  • TypeScript Handbook — Enums (TypeScript)
  • TypeScript 3.4 Release Notes — const assertions (TypeScript)
  • TypeScript Handbook — Literal Types (TypeScript)
  • TypeScript Handbook — Template Literal Types (TypeScript)
  • TypeScript 5.0 Release Notes — union enums (TypeScript)