Guide pratique de l'opérateur `satisfies` en TypeScript
Explication de l opérateur satisfies de TypeScript avec exemples de config, inférence étroite et usage face à as ou aux annotations de type.
L’opérateur satisfies vérifie qu’une valeur est conforme à un type sans modifier le type inféré de cette valeur — vous bénéficiez ainsi à la fois de la sécurité du type et de la précision de la valeur. Ce comportement unique résout un problème courant et bien précis : une annotation par deux-points sur un objet de configuration vous protège contre les valeurs incorrectes, mais efface les clés littérales et les types étroits que vous souhaitiez conserver. Ce guide vous propose un modèle mental, l’exemple canonique et une règle de décision pour choisir entre satisfies, as ou une simple annotation par deux-points. Tous les exemples de code sont écrits pour TypeScript 4.9 et versions ultérieures.
Points clés à retenir
satisfiesvalide une valeur par rapport à un type tout en préservant le type inféré étroit de la valeur, de sorte que l’autocomplétion et le rétrécissement des types littéraux restent opérationnels.- Avec une annotation par deux-points, le type déclaré prend le dessus et la valeur est élargie pour lui correspondre ; avec
satisfies, c’est la valeur qui prend le dessus et le type n’est utilisé que pour la valider. asne vérifie pas votre valeur — il contourne le vérificateur de types, ce qui explique pourquoiconst user = {} as Usercompile sans erreur mais lève une exception à l’exécution dès que vous accédez àuser.name.- Combiner une annotation et
satisfies(const x: T = {…} satisfies T) est redondant : l’annotation par deux-points prend la priorité et annule le rétrécissement que vous souhaitiez obtenir. satisfiesn’existe qu’à la compilation et n’émet aucun JavaScript ; faites appel à un validateur d’exécution comme Zod lorsque des données proviennent d’un réseau ou d’un fichier.
Le problème que satisfies résout
Deux situations poussent les développeurs vers satisfies. La première est une annotation par deux-points sur un objet à clés, qui élargit le type et détruit l’autocomplétion. L’exemple routes de Matt Pocock l’illustre parfaitement : annotez avec Record<string, {}> et vous pouvez lire n’importe quelle clé, même une clé absurde, sans déclencher d’erreur.
const routes: Record<string, {}> = {
"/": {},
"/users": {},
"/admin/users": {},
};
routes.awdkjanwdkjn; // Aucune erreur — le type est désormais Record<string, {}>
La seconde situation concerne une propriété dont le type est une union. Dans l’exemple de freeCodeCamp, une propriété typée comme une union entre un littéral de chaîne et un objet ne permet pas d’appeler des méthodes de chaîne sans une vérification manuelle.
type Info = "John" | "Jack" | { id: number; age: number };
type Person = { myInfo: Info; myOtherInfo: Info };
const applicant: Person = { myInfo: "John", myOtherInfo: { id: 123, age: 22 } };
applicant.myInfo.toUpperCase();
// La propriété 'toUpperCase' n'existe pas sur le type 'Info'
Vous finissez par écrire if (typeof applicant.myInfo === "string") avant chaque accès. Ces deux problèmes partagent la même cause racine : l’annotation par deux-points a remplacé votre type de valeur spécifique par un type déclaré plus large.
Que fait l’opérateur satisfies ?
Discover how at OpenReplay.com.
satisfies valide qu’une expression correspond à un type sans modifier le type que TypeScript lui infère. Il a été introduit dans TypeScript 4.9, publié le 15 novembre 2022, et son comportement est identique dans l’actuelle version stable TypeScript 6.0 ainsi que dans le release candidate 7.0. Le portage en Go de la version 7.0 ayant conservé une sémantique de vérification de types structurellement identique à celle de la version 6.0, satisfies applique exactement les mêmes règles sur le nouveau compilateur.
Le modèle mental, selon la formulation de Pocock : avec une annotation par deux-points, le type l’emporte sur la valeur ; avec satisfies, c’est la valeur qui l’emporte sur le type. Lorsque vous utilisez satisfies, TypeScript infère le type le plus étroit possible et n’utilise l’annotation que pour le valider. Il s’agit d’un mécanisme purement à la compilation — il n’émet aucun JavaScript et n’a aucun coût à l’exécution, ce qui lui permet de détecter une faute de frappe ou un type de valeur incorrect avant même que le code ne s’exécute.
L’exemple canonique de satisfies
La solution consiste à déplacer l’annotation du deux-points vers un satisfies en fin d’expression : les deux problèmes disparaissent simultanément — vous conservez les types littéraux étroits et vous obtenez toujours une erreur en cas de valeur incorrecte.
const routes = {
"/": {},
"/users": {},
"/admin/users": {},
} satisfies Record<string, {}>;
routes.awdkjanwdkjn;
// La propriété 'awdkjanwdkjn' n'existe pas sur le type
// '{ "/": {}; "/users": {}; "/admin/users": {}; }'
L’autocomplétion sur routes liste désormais les vrais chemins. La validation reste en place : assignez quelque chose que l’annotation interdit et le compilateur le rejette.
const routes = {
"/": null, // Le type 'null' n'est pas assignable au type '{}'
} satisfies Record<string, {}>;
La même technique résout le cas de l’union. applicant.myInfo est rétréci au littéral "John", donc .toUpperCase() est légal sans aucune vérification :
const applicant = {
myInfo: "John",
myOtherInfo: { id: 123, age: 22 },
} satisfies Person;
applicant.myInfo.toUpperCase(); // OK — inféré comme "John"
satisfies vs as vs annotation par deux-points
as ne vérifie pas votre valeur — il contourne le vérificateur de types, ce qui explique pourquoi const user = {} as User compile sans erreur puis lève une exception à l’exécution dès que vous accédez à user.name. C’est là la distinction fondamentale entre ces trois outils :
| Outil | Vérifie la valeur ? | Conserve l’inférence étroite ? | Peut-on tromper TS ? | Utiliser quand |
|---|---|---|---|---|
: Type (deux-points) | Oui | Non — élargit au type | Non | Vous souhaitez délibérément un type plus large |
satisfies Type | Oui | Oui | Non | Vous voulez la validation et l’inférence étroite |
as Type | Non | N/A | Oui | Presque jamais par défaut |
Le danger à l’exécution est concret :
type User = { id: string; name: { first: string; last: string } };
const user = {} as User;
user.name.first; // Aucune erreur dans l'IDE — lève une exception à l'exécution
as se dégrade aussi silencieusement. Ce code compile aujourd’hui, mais ajoutez un champ obligatoire à User et defaultUser devient invalide sans aucune erreur :
type User = { id: string; name: string };
const defaultUser = { id: "123", name: "Matt" } as User;
C’est précisément ce type de bug que le rejeu de session est conçu à faire remonter : vous voyez la forme réelle de l’objet au moment où l’accès à une propriété a échoué, et non la forme que vous aviez affirmée. Remplacez as par satisfies et le compilateur signale immédiatement le champ manquant.
La règle empirique : ne recourez jamais à as par défaut — utilisez satisfies pour valider tout en conservant l’inférence, et réservez l’annotation par deux-points aux cas où vous souhaitez délibérément un type plus large que vous réassignerez ultérieurement.
Le piège de l’annotation redondante
Combiner une annotation et satisfies — const joe: TUser = {…} satisfies TUser — est redondant : l’annotation par deux-points prend la priorité et annule silencieusement le rétrécissement que satisfies était censé préserver. L’article de Refine.dev en montre les conséquences — l’accès à une propriété imbriquée échoue parce que le type déclaré a pris le dessus et que le rétrécissement interne a été abandonné. Choisissez l’un ou l’autre. Si vous voulez le rétrécissement, supprimez le deux-points.
Là où satisfies fait vraiment la différence
Utilisez satisfies pour les configurations typées, les maps à clés de type Record et les valeurs d’unions discriminées que vous souhaitez conserver rétrécies. Les maps de thèmes et de palettes de couleurs en sont l’archétype — l’exemple officiel de palette valide chaque entrée RGB tout en conservant les types littéraux par clé. Pour les clés optionnelles, encapsulez le record dans Partial afin que les clés absentes soient autorisées, mais que les clés présentes soient tout de même vérifiées :
type Keys = "id" | "name" | "email" | "age";
const person = {
id: 12345,
name: "Jacky",
email: "jacky@test.com",
} satisfies Partial<Record<Keys, string | number>>;
person.name.toUpperCase(); // rétréci à string
Quand ne pas utiliser satisfies
Évitez satisfies pour un objet simple où une annotation : Type suffit à exprimer tout ce dont vous avez besoin. Évitez-le également lorsque vous souhaitez le type plus large — si vous prévoyez de réassigner une variable ultérieurement, satisfies vous en empêche car il verrouille le type inféré étroit :
// Annotation par deux-points — la réassignation fonctionne
let id: string | number = "123";
id = 456; // OK
// satisfies — la valeur l'emporte, donc le type est rétréci à string
let id2 = "123" satisfies string | number;
id2 = 456; // Le type 'number' n'est pas assignable au type 'string'
Et évitez-le entièrement pour les données que vous ne contrôlez pas. satisfies ne s’exécute jamais, il ne peut donc pas valider un payload JSON ou une soumission de formulaire à l’exécution — faites appel à un validateur de schéma à l’exécution comme Zod ou io-ts lorsque les données franchissent une frontière réseau ou fichier.
Faites appel à satisfies chaque fois que vous typez un littéral que vous souhaitez également conserver précis — configurations, maps de routes, palettes, valeurs d’unions. Remplacez votre as réflexe par satisfies, réservez les annotations par deux-points aux cas où un type plus large est l’objectif, et votre prochain objet de configuration alliera sécurité et autocomplétion.
Questions fréquentes
L'opérateur satisfies fonctionne-t-il dans les fichiers JavaScript avec JSDoc ?
Oui. TypeScript 5.0 a ajouté un tag JSDoc @satisfies qui fait exactement ce que fait l'opérateur satisfies dans les fichiers TypeScript. Dans un fichier JavaScript, vous écrivez le tag au-dessus d'une déclaration, par exemple une annotation @satisfies nommant un type, et le vérificateur valide la valeur par rapport à ce type tout en conservant le type inféré étroit. Cela permet aux projets JavaScript typés avec JSDoc de bénéficier du même avantage de validation avec rétrécissement, sans avoir à migrer vers des fichiers .ts.
satisfies ajoute-t-il un coût à l'exécution ou génère-t-il du JavaScript supplémentaire ?
Non. satisfies est un opérateur purement à la compilation, au niveau des types, qui n'émet aucun JavaScript — il n'a donc aucun coût à l'exécution et aucun impact sur la taille du bundle. Le mot-clé et le type qui le suit sont effacés lors de la compilation, exactement comme une annotation par deux-points. Puisque rien ne s'exécute, il ne peut pas non plus valider des données à l'exécution, ce qui explique pourquoi les payloads réseau ou les saisies de formulaires nécessitent toujours un validateur de schéma à l'exécution comme Zod.
Pourquoi mon objet échoue-t-il toujours à la vérification de types lorsque j'utilise à la fois une annotation par deux-points et satisfies ?
Parce que l'annotation par deux-points prend toujours la priorité et la clause satisfies devient une syntaxe inopérante. Écrire const config: Theme = {…} satisfies Theme signifie que le type Theme déclaré l'emporte, la valeur est donc élargie à Theme et le rétrécissement que satisfies était censé préserver est abandonné. L'accès à une propriété littérale imbriquée échoue alors comme si satisfies était absent. Supprimez le deux-points et conservez uniquement le satisfies en fin d'expression pour maintenir le rétrécissement.
Quand devrais-je utiliser Zod plutôt que satisfies pour valider des données ?
Utilisez Zod, ou un autre validateur de schéma à l'exécution comme io-ts, chaque fois que les données arrivent à l'exécution depuis une source que vous ne contrôlez pas, comme une réponse réseau, un fichier JSON ou une soumission de formulaire. satisfies ne vérifie que les littéraux que vous écrivez dans votre code source à la compilation et n'émet aucun code à l'exécution — il ne peut donc pas inspecter des données entrantes inconnues. Utilisez satisfies pour les configurations typées, les maps de type Record et les valeurs d'unions dans votre propre code ; utilisez Zod pour les frontières avec l'extérieur.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k