Literal types, widening, as const y satisfies | Nicolás Garzón
TypeScript puede representar un valor exacto mediante un literal type .
TypeScript
Copiar type Status = "idle" | "loading" | "success" ; TypeScript
Copiar let direction: "left" | "right" ;
direction = "left" ;
direction = "right" ; Cada string del union es un tipo que contiene un solo valor.
También existen literales numéricos y booleanos:
TypeScript
Copiar type Dice = 1 | 2 | 3 | 4 | 5 | 6 ;
type Enabled = true ; TypeScript
Copiar const fixedStatus = "idle" ;
let changingStatus = "idle" ; Tipos inferidos conceptualmente:
Texto
Copiar fixedStatus → "idle"
changingStatus → stringUn const no puede reasignarse, por lo que conservar el valor literal es seguro.
Un let puede recibir otros strings y normalmente se amplía.
TypeScript
Copiar const request = {
method: "GET" ,
} ; Aunque el binding request no puede reasignarse, la propiedad sí:
TypeScript
Copiar request. method = "POST" ; Por eso method suele inferirse como string, no como "GET".
TypeScript
Copiar const request: {
method: "GET" | "POST" ;
} = {
method: "GET" ,
} ; La anotación declara las opciones permitidas.
TypeScript
Copiar const request = {
method: "GET" ,
headers: [ "Accept" ] ,
} as const ; El resultado se aproxima a:
TypeScript
Copiar {
readonly method: "GET" ;
readonly headers: readonly [ "Accept" ] ;
}
Conserva literal types.
Convierte propiedades del literal en readonly a nivel de tipos.
Convierte arrays literales en readonly tuples.
No congela el objeto en runtime.
TypeScript
Copiar const headers = [ "Accept" ] ;
const request = {
method: "GET" ,
headers,
} as const ; request.headers no puede reasignarse, pero el array externo continúa siendo mutable:
TypeScript
Copiar headers. push ( "Authorization" ) ; as const conserva la estructura literal; no crea una inmutabilidad profunda real sobre referencias que ya existían.
TypeScript
Copiar const method = "GET" as "GET" ; Funciona, pero suele ser redundante con const y puede esconder una mala inferencia si se utiliza indiscriminadamente.
TypeScript
Copiar type Routes = Record< string , string > ;
const routes: Routes = {
home: "/" ,
products: "/products" ,
} ; La variable queda vista como Record<string, string>.
TypeScript
Copiar routes. home; Dependiendo del contrato, TypeScript puede perder conocimiento preciso de las claves concretas y tratarlas como strings dinámicos.
TypeScript
Copiar const routes = {
home: "/" ,
products: "/products" ,
} satisfies Record< string , string > ; satisfies comprueba que el valor sea compatible sin reemplazar su tipo resultante por el tipo objetivo.
El checker conserva las claves conocidas:
TypeScript
Copiar routes. home;
routes. products; TypeScript
Copiar const configuration = {
retries: "3" ,
} satisfies {
retries: number ;
} ; Produce un error. No convierte el string.
TypeScript
Copiar const value = externalValue as Product; as pide al checker aceptar una interpretación.
TypeScript
Copiar const value = {
id: "product-1" ,
} satisfies Product; satisfies verifica que una expresión conocida cumpla el contrato.
No se utiliza para validar un unknown recibido en runtime.
TypeScript
Copiar const palette: Record< string , string | number [ ] > = {
red: "#ff0000" ,
green: [ 0 , 255 , 0 ] ,
} ; El binding utiliza el tipo declarado.
TypeScript
Copiar const palette = {
red: "#ff0000" ,
green: [ 0 , 255 , 0 ] ,
} satisfies Record< string , string | number [ ] > ; Comprueba el contrato y suele conservar más información específica de cada propiedad.
TypeScript
Copiar type RouteName = "home" | "products" ;
const routes = {
home: "/" ,
products: "/products" ,
settings: "/settings" ,
} satisfies Record< RouteName, string > ; La clave adicional produce un error durante esta comprobación del literal.
TypeScript
Copiar type Role = "admin" | "customer" | "delivery" ; Para muchos estados simples, un union literal:
No genera JavaScript.
Interopera directamente con strings.
Se deriva fácilmente desde objetos as const.
enum tiene un contrato de emisión distinto y se estudiará por separado.
TypeScript
Copiar const roles = {
admin: "admin" ,
customer: "customer" ,
delivery: "delivery" ,
} as const ;
type Role = typeof roles[ keyof typeof roles] ; TypeScript
Copiar type Role = "admin" | "customer" | "delivery" ; La fuente runtime y el tipo pueden mantenerse alineados.
TypeScript
Copiar type RouteConfiguration = {
path: ` / ${ string } ` ;
requiresAuthentication: boolean ;
} ;
const routes = {
home: {
path: "/" ,
requiresAuthentication: false ,
} ,
dashboard: {
path: "/dashboard" ,
requiresAuthentication: true ,
} ,
} satisfies Record< string , RouteConfiguration> ; El objeto se valida, pero conserva home y dashboard como claves conocidas.
Creer que const vuelve inmutable un objeto.
Utilizar as const esperando Object.freeze.
Aplicar assertions para forzar cualquier literal.
Confundir satisfies con validación runtime.
Anotar un objeto y perder precisión que sí necesitabas.
Usar string en estados que tienen opciones cerradas.
Introducir enum cuando un union literal expresa mejor el contrato.
Esperar inmutabilidad profunda de referencias externas.
Un literal type representa un valor exacto.
let suele ampliar literales mutables.
Las propiedades de un objeto const todavía pueden cambiar.
as const conserva literales y añade readonly estático.
as const no congela runtime.
satisfies comprueba compatibilidad y conserva inferencia útil.
Una annotation cambia el tipo del binding.
Una assertion pide confianza en lugar de demostrarla.
¿Por qué const request = { method: "GET" } suele inferir method como string?
Respuesta Porque const impide reasignar el binding request, pero la propiedad method continúa siendo mutable y podría recibir otro string.
Tipos básicos y tipos de propósito especial organiza los valores que el sistema puede representar y evita nombres engañosos como String u Object.