Menu

L'opérateur satisfies en TypeScript : face aux annotations et à as

L'opérateur satisfies vérifie qu'une valeur correspond à un type sans changer le type inféré de la valeur. Découvrez ce qu'il fait, comment il se compare à une annotation de type et à as (le même objet écrit de trois façons), comment il se combine avec as const, et pourquoi il convient aux objets de configuration.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

value satisfies Type vérifie à la compilation que value correspond à Type, puis laisse tranquille le type propre, plus précis, de la valeur. Une annotation remplacerait ce type précis par Type ; satisfies valide sans élargir.

satisfies fait tout de même la vérification : une couleur manquante, une clé mal orthographiée comme bleu, ou une valeur comme true est une erreur de compilation sur cette ligne. Il existe depuis TypeScript 4.9 et, comme toute annotation de type, il est retiré du JavaScript émis.

Le problème que résout satisfies

Avec une annotation de type, le type de la variable est l'annotation. Le compilateur oublie ce qu'il a vu dans le littéral. Ici, la même palette est annotée à la place, et TypeScript ne sait plus que green est une chaîne :

Le compilateur signale :

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

Avant TypeScript 4.9, les choix étaient : annoter et affiner à la main partout (typeof palette.green === "string"), ou renoncer à l'annotation et perdre la vérification. satisfies donne les deux. Remplacez : Record<ColorName, Color> par satisfies Record<ColorName, Color> après l'accolade fermante et le code s'exécute.

satisfies, annotation de type ou as

Le même objet de réglages, écrit de trois façons :

as a laissé passer le lang manquant, et asserted.lang vaut undefined à l'exécution alors que son type indique string. Retirez lang des deux autres lignes et toutes deux échouent avec TS2741, Property 'lang' is missing in type ....

Annotation const x: T = vAssertion v as Tv satisfies T
Propriétés manquanteserreurautoriséeserreur
Propriétés en trop (littéral objet)erreurautoriséeserreur
Mauvais type de propriétéerreurseulement si les types ne se recouvrent paserreur
Type de x ensuiteTTle type inféré de v
Types littéraux ("dark", 8080)élargis en Télargis en Tconservés là où T les autorise
Clés d'un Record<string, ...>n'importe quelle chaîne (les fautes de frappe compilent)n'importe quelle chaîneexactement les clés écrites
Effet à l'exécutionaucunaucunaucun

Règle empirique : annotez quand vous voulez que la variable ait le type déclaré (une valeur que vous réassignerez, une API publique), et utilisez satisfies quand vous voulez une vérification mais que le type propre de la valeur est plus utile.

Détecter les erreurs dans les littéraux objet

satisfies exécute la vérification d'assignabilité complète, y compris la vérification des propriétés en trop : les fautes de frappe dans les clés sont donc des erreurs.

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 vérification donne aussi au littéral un type contextuel, comme le fait une annotation. Cela compte de deux façons. Les littéraux de chaîne sont conservés comme types littéraux quand le type cible les attend : { path: "/", method: "GET" } satisfies Route a method: "GET", alors que le même objet sans annotation inférerait method: string. Et les paramètres des callbacks sont inférés à partir du type cible :

Les clés d'un Record restent connues

Un usage courant est la table de correspondance. Annotée Record<string, T>, toute chaîne est une clé valide et une faute de frappe compile, en renvoyant undefined à l'exécution. Avec satisfies, les valeurs sont toujours vérifiées par rapport à T, mais le type de la variable liste exactement les clés que vous avez écrites :

keyof typeof endpoints n'est utile que parce que les clés ont survécu. Avec l'annotation, ce serait un simple string.

Pour exiger un ensemble de clés fixe, satisfaites un Record sur une union : satisfies Record<"dev" | "prod", string> signale un prod manquant avec TS2741 et un staging inconnu avec TS2353.

as const satisfies

as const et satisfies se combinent. Écrivez as const en premier : il rend la valeur readonly en profondeur avec des types littéraux, puis satisfies vérifie cette valeur exacte.

Chaque route est vérifiée par rapport à Route (un method: "PUT" serait une erreur), et le tuple de types littéraux reste disponible : Path est donc une union des vrais chemins. Utilisez readonly Route[] (ou ReadonlyArray<Route>) comme cible, puisqu'un tableau as const est readonly.

Objets de configuration

La configuration, c'est là que satisfies se rend indispensable : la forme doit être juste, et le code ailleurs a besoin des valeurs précises.

Oubliez l'entrée production, faites une faute dans logLevel, ou écrivez logLevel: "verbose", et le compilateur désigne la ligne exacte. Le même motif convient aux fichiers *.config.ts : export default { ... } satisfies SomeConfig vérifie tout le fichier tandis que l'objet exporté garde ses valeurs littérales.

Quand ne pas utiliser satisfies

  • La variable sera réassignée. let cfg = { port: 3000 } satisfies { port: number | string } donne à cfg le type { port: number }, donc un cfg = { port: "80" } ultérieur échoue (TS2322). Annotez les variables que vous comptez modifier.
  • Vous voulez volontairement le type déclaré. Pour la valeur de retour d'une fonction ou une constante exportée qui fait partie d'une API, le type de l'annotation est le contrat, et exposer le type littéral exact peut rendre les changements ultérieurs cassants.
  • La valeur n'est pas un littéral. satisfies brille sur les littéraux d'objets et de tableaux. Sur une variable ou le résultat d'un appel, c'est une simple vérification d'assignabilité, ce qu'une annotation vous donne déjà.

Questions fréquentes

Que fait satisfies en TypeScript ?

expression satisfies Type vérifie à la compilation que l'expression est assignable à Type, en signalant les propriétés manquantes, les propriétés en trop et les mauvais types de valeurs, puis laisse inchangé le type inféré de l'expression. Vous obtenez la sécurité d'une annotation et la précision de l'inférence. Il est effacé de la sortie JavaScript.

Quelle est la différence entre satisfies et une annotation de type ?

Les deux vérifient la valeur. Une annotation (const x: T = ...) donne ensuite à la variable le type T, en oubliant ce que le compilateur savait de la valeur (types littéraux, membre de l'union auquel appartient chaque propriété, clés existantes). satisfies T garde le type inféré : x.someKey est connu comme existant, et une propriété string | number qui contient une chaîne est typée string.

Quelle est la différence entre satisfies et as en TypeScript ?

as est une assertion : il remplace le type et ne vérifie presque rien, si bien que les propriétés manquantes passent inaperçues. satisfies est une vérification : la valeur doit réellement correspondre au type, et son propre type inféré est conservé. Quand les deux compileraient, satisfies est le choix le plus sûr.

Que signifie as const satisfies ?

Les deux s'appliquent : as const rend la valeur readonly en profondeur avec des types littéraux, puis satisfies vérifie ce résultat par rapport à un type. Écrivez as const en premier : const routes = [...] as const satisfies readonly Route[];. La variable garde les types littéraux exacts pour la suite, et une entrée erronée reste une erreur de compilation.

Quelle version de TypeScript a ajouté satisfies ?

TypeScript 4.9, sorti en novembre 2022. C'est une syntaxe effaçable ordinaire : elle fonctionne donc aussi avec le type stripping intégré de Node, et toutes les versions actuelles de TypeScript (7 compris) la prennent en charge.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER