Menu

El operador satisfies en TypeScript: frente a anotaciones y as

El operador satisfies comprueba que un valor encaja con un tipo sin cambiar el tipo inferido del valor. Aprende qué hace, cómo se compara con una anotación de tipo y con as (el mismo objeto escrito de tres formas), cómo se combina con as const y por qué encaja con los objetos de configuración.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

value satisfies Type comprueba en tiempo de compilación que value encaja con Type, y después deja en paz el tipo propio del valor, más preciso. Una anotación sustituiría ese tipo preciso por Type; satisfies valida sin ensanchar.

satisfies sigue haciendo la comprobación: un color que falta, una clave mal escrita como bleu o un valor como true son un error de compilación en esa línea. Existe desde TypeScript 4.9 y, como todas las anotaciones de tipo, se elimina del JavaScript generado.

El problema que resuelve satisfies

Con una anotación de tipo, el tipo de la variable es la anotación. El compilador olvida lo que vio en el literal. Aquí la misma paleta está anotada, y ahora TypeScript ya no sabe que green es un string:

El compilador informa:

index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
  Property 'toUpperCase' does not exist on type '[number, number, number]'.

Antes de TypeScript 4.9 las opciones eran: anotar y estrechar a mano en todas partes (typeof palette.green === "string"), o prescindir de la anotación y perder la comprobación. satisfies da las dos cosas. Cambia : Record<ColorName, Color> por satisfies Record<ColorName, Color> después de la llave de cierre y se ejecuta.

satisfies frente a anotación de tipo y as

El mismo objeto de configuración, escrito de tres formas:

as dejó pasar la falta de lang, y asserted.lang es undefined en tiempo de ejecución aunque su tipo diga string. Quita lang de las otras dos líneas y ambas fallan con TS2741, Property 'lang' is missing in type ....

Anotación const x: T = vAserción v as Tv satisfies T
Propiedades que faltanerrorpermitidoerror
Propiedades de más (objeto literal)errorpermitidoerror
Tipo de propiedad incorrectoerrorsolo si los tipos no se solapanerror
Tipo de x despuésTTel tipo inferido de v
Tipos literales ("dark", 8080)ensanchados a Tensanchados a Tse conservan donde T los permite
Claves de un Record<string, ...>cualquier string (las erratas compilan)cualquier stringexactamente las claves escritas
Efecto en tiempo de ejecuciónningunoningunoninguno

Regla práctica: anota cuando quieras que la variable tenga el tipo declarado (un valor que vas a reasignar, una API pública), y usa satisfies cuando quieras una comprobación pero el tipo propio del valor sea más útil.

Detectar errores en objetos literales

satisfies hace la comprobación de asignabilidad completa, incluida la de propiedades de más, así que las erratas en las claves son errores:

type Route = { path: string; method: "GET" | "POST" };

const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?

La comprobación también da al literal un tipo contextual, igual que una anotación. Eso importa de dos maneras. Los literales de string se conservan como tipos literales cuando el tipo destino los espera: { path: "/", method: "GET" } satisfies Route tiene method: "GET", mientras que el mismo objeto sin anotación inferiría method: string. Y los parámetros de los callbacks se infieren a partir del tipo destino:

Las claves de Record siguen siendo conocidas

Un uso habitual es una tabla de búsqueda. Anotada como Record<string, T>, cualquier string es una clave válida y una errata compila, devolviendo undefined en tiempo de ejecución. Con satisfies, los valores se siguen comprobando contra T, pero el tipo de la variable enumera exactamente las claves que escribiste:

keyof typeof endpoints solo es útil porque las claves sobrevivieron. Con la anotación sería simplemente string.

Para exigir un conjunto fijo de claves, usa satisfies con un Record sobre una unión: satisfies Record<"dev" | "prod", string> informa de un prod que falta con TS2741 y de un staging desconocido con TS2353.

as const satisfies

as const y satisfies se combinan. Escribe as const primero: hace el valor readonly en profundidad con tipos literales, y después satisfies comprueba ese valor exacto.

Cada ruta se comprueba contra Route (un method: "PUT" sería un error), y la tupla de tipos literales sigue disponible, así que Path es una unión de las rutas reales. Usa readonly Route[] (o ReadonlyArray<Route>) como destino, ya que un array as const es readonly.

Objetos de configuración

La configuración es donde satisfies se gana su sitio: la forma tiene que ser correcta, y el código de otras partes quiere los valores precisos.

Olvida la entrada production, escribe mal logLevel o pon logLevel: "verbose", y el compilador señala la línea exacta. El mismo patrón sirve para los archivos *.config.ts: export default { ... } satisfies SomeConfig comprueba todo el archivo mientras el objeto exportado conserva sus valores literales.

Cuándo no usar satisfies

  • La variable se va a reasignar. let cfg = { port: 3000 } satisfies { port: number | string } da a cfg el tipo { port: number }, así que un cfg = { port: "80" } posterior falla (TS2322). Anota las variables que piensas cambiar.
  • Quieres a propósito el tipo declarado. Para el valor de retorno de una función o una constante exportada que forma parte de una API, el tipo de la anotación es el contrato, y filtrar el tipo literal exacto puede hacer que cambios posteriores rompan cosas.
  • El valor no es un literal. satisfies brilla con objetos y arrays literales. Sobre una variable o el resultado de una llamada es una simple comprobación de asignabilidad, que ya te da una anotación.

Preguntas frecuentes

¿Qué hace satisfies en TypeScript?

expression satisfies Type comprueba en tiempo de compilación que la expresión se puede asignar a Type, informando de propiedades que faltan, propiedades de más y tipos de valor incorrectos, y después deja sin cambiar el tipo inferido de la expresión. Tienes la seguridad de una anotación y la precisión de la inferencia. Se borra de la salida en JavaScript.

¿Qué diferencia hay entre satisfies y una anotación de tipo?

Los dos comprueban el valor. Una anotación (const x: T = ...) da luego a la variable el tipo T, olvidando lo que el compilador sabía del valor (tipos literales, qué miembro de la unión es cada propiedad, qué claves existen). satisfies T conserva el tipo inferido, así que se sabe que x.someKey existe y una propiedad string | number que contiene un string tiene tipo string.

¿Qué diferencia hay entre satisfies y as en TypeScript?

as es una aserción: sustituye el tipo y no comprueba casi nada, así que las propiedades que faltan pasan desapercibidas. satisfies es una comprobación: el valor tiene que encajar de verdad con el tipo, y se conserva su propio tipo inferido. Cuando compilarían los dos, satisfies es la opción más segura.

¿Qué significa as const satisfies?

Aplica las dos cosas: as const hace el valor readonly en profundidad con tipos literales, y después satisfies comprueba ese resultado contra un tipo. Escribe as const primero: const routes = [...] as const satisfies readonly Route[];. La variable conserva los tipos literales exactos para usarlos después, y una entrada incorrecta sigue siendo un error de compilación.

¿Qué versión de TypeScript añadió satisfies?

TypeScript 4.9, publicado en noviembre de 2022. Es sintaxis borrable, así que también funciona con el type stripping integrado de Node, y todas las versiones actuales de TypeScript (incluida la 7) lo admiten.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR