Um literal type é um tipo com exatamente um valor: "up" é um tipo cujo único membro é a string "up", e 404 é um tipo cujo único membro é o número 404. Sozinhos eles não ajudam muito. Juntos em uma union, dão a você uma variável que aceita um conjunto fixo de valores e nada mais.
Sem o comentário @ts-expect-error, a última chamada é um erro de compilação (TS2345). Com ele, o programa compila e a chamada ainda roda e imprime moving north by 1: literal types existem só para o compilador, e em tempo de execução o valor é uma string comum.
Literais de string, number e boolean
Qualquer valor string, number, bigint ou boolean pode ser escrito como tipo. O compilador então aceita só aquele valor exato.
let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;
type Port = 80 | 443 | 8080;
const port: Port = 443;
O próprio boolean é só a union true | false, e é por isso que fazer narrowing de um boolean com if (flag) deixa false no else.
| Literal type | Aceita | Tipo mais amplo |
|---|---|---|
"GET" | só a string "GET" | string |
404 | só o número 404 | number |
10n | só o bigint 10 | bigint |
true | só true | boolean |
Unions de literais
O uso comum é uma union que lista todos os valores permitidos. Dentro da função, o compilador faz o narrowing da union conforme você a verifica, então cada ramo sabe exatamente qual valor tem.
Uma union de literais de string é a alternativa usual a um enum no TypeScript. Não custa nada em tempo de execução, os valores são strings comuns que você pode imprimir no log e enviar em JSON, e um erro de digitação é erro de compilação. Mais sobre as vantagens e desvantagens em enums.
Widening: let vs const
Quando o TypeScript infere um tipo a partir de um literal, ele olha se o valor pode mudar. Uma variável const nunca pode ser reatribuída, então mantém o literal type. Uma variável let pode, então o tipo dela é alargado para o tipo geral.
Passe o mouse sobre cada nome no editor para ver o tipo inferido. Se você quer um let que só guarde certos valores, anote-o: let mode: "light" | "dark" = "light".
Por que as propriedades de objeto são alargadas
Propriedades de um objeto literal são mutáveis, então também são alargadas, mesmo quando o objeto está guardado em um const. É o jeito mais comum de esbarrar em literal types sem querer:
O compilador mostra:
index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
req é inferido como { url: string; method: string } porque um código mais adiante poderia rodar req.method = "DELETE". Há três soluções:
Uma quarta opção é o satisfies, que verifica o objeto contra um tipo mantendo os literal types das propriedades.
as const
as const é uma const assertion. Coloque depois de uma expressão e o compilador infere o tipo mais estreito possível:
- valores string, number e boolean mantêm os literal types
- propriedades de objeto viram
readonly - literais de array viram tuplas
readonlycom tamanho fixo
A assertion vale só em tempo de compilação. O JavaScript gerado é o mesmo objeto literal sem o as const, então nada impede outro código de alterá-lo em tempo de execução. Se você precisa de uma garantia em tempo de execução, chame também Object.freeze.
Um union type a partir de um array as const
Um padrão frequente é manter os valores permitidos em um array, que você pode percorrer em tempo de execução, e derivar o union type dele. (typeof arr)[number] significa "o tipo de qualquer elemento de arr".
Sem as const, ROLES seria string[] e Role seria só string. O cast para readonly string[] em isRole é necessário porque includes em uma tupla de literais só aceita esses literais, e o objetivo da função é testar uma string que pode não ser um deles. O mesmo padrão com objeto funciona para mapas de chave e valor: const Status = { Active: "active", Banned: "banned" } as const e depois type Status = (typeof Status)[keyof typeof Status].
const type parameters
Uma função genérica normalmente alarga os literais que você passa. Desde o TypeScript 5.0, você pode marcar um type parameter como const, o que faz o compilador inferir o argumento como se ele tivesse as const, sem pedir que quem chama escreva isso.
É principalmente uma ferramenta para autores de bibliotecas: definições de rotas, builders e helpers de schema a usam para que quem chama receba tipos precisos a partir de literais simples.
Os significados de const
A palavra const aparece em quatro lugares diferentes no código TypeScript:
| Sintaxe | Tipo | O que faz |
|---|---|---|
const x = 1 | Declaração JavaScript | a variável não pode ser reatribuída; um valor literal mantém o literal type |
expr as const | Assertion do TypeScript | tipo mais estreito: literais, propriedades readonly, tuplas readonly |
function f<const T>() | Type parameter do TypeScript | infere os argumentos como se tivessem as const |
const enum E {} | Enum do TypeScript | um enum cujos membros são substituídos pelo valor na compilação |
Nenhum deles congela um objeto em tempo de execução. const obj = { a: 1 } ainda permite obj.a = 2; só reatribuir o próprio obj é um erro.
Perguntas frequentes
O que é um literal type no TypeScript?
Um tipo que permite exatamente um valor. "GET" é um tipo cujo único valor é a string "GET", 404 é um tipo cujo único valor é o número 404, e true é um tipo cujo único valor é true. Eles são mais úteis combinados em unions, como type Method = "GET" | "POST".
O que o as const faz no TypeScript?
as const é uma const assertion. Ele diz ao compilador para inferir o tipo mais estreito de uma expressão: valores string e number mantêm os literal types, propriedades de objeto viram readonly e literais de array viram tuplas readonly. Ele muda só o tipo; em tempo de execução o valor é o mesmo objeto ou array comum, e não fica congelado.
Por que o TypeScript infere string em vez do meu literal?
Porque o valor é mutável. let x = "a" e a propriedade em { method: "GET" } podem ser reatribuídos depois, então o TypeScript os alarga para string. Uma variável const mantém o literal type "a". Para manter literais dentro de um objeto, anote-o com o literal type, use as const ou use satisfies.
Qual é a diferença entre const e as const?
const é uma declaração do JavaScript: a variável não pode ser reatribuída, mas o objeto para o qual ela aponta ainda pode mudar. as const é uma type assertion do TypeScript: torna toda propriedade do valor readonly e literal no sistema de tipos. Nenhum dos dois congela o objeto em tempo de execução; para isso, use Object.freeze.
Como obter um union type a partir de um array de strings?
Declare o array com as const e depois indexe o tipo dele com number: const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; dá "admin" | "user". Sem as const, o array é string[] e o resultado é apenas string.