Journal

L'opérateur satisfies et les unions discriminées bien typées

Journal du dimanche 23 août 2026, généré le 23 août à 19:38

L'opérateur satisfies et les unions discriminées bien typées

Deux outils du typage TypeScript restent sous-utilisés une fois les bases acquises : l'opérateur satisfies (depuis TS 4.9) et la construction rigoureuse des unions discriminées. Les deux résolvent un même problème récurrent : garder un typage précis sans sacrifier l'inférence.

Le problème classique de l'annotation

Annoter une variable avec un type large pour la valider fait perdre l'inférence fine des littéraux :

type Couleur = "rouge" | "vert" | "bleu";

const palette: Record<string, Couleur> = {
  fond: "rouge",
  texte: "bleu",
};

palette.fond.toUpperCase(); // OK, mais palette.fond est vu comme Couleur, pas "rouge"

Avec Record<string, Couleur>, TypeScript oublie que fond existe précisément et autorise palette.absent sans erreur à la lecture. satisfies vérifie la conformité au type tout en conservant le type inféré le plus précis :

const palette = {
  fond: "rouge",
  texte: "bleu",
} satisfies Record<string, Couleur>;

palette.fond; // typé "rouge", pas Couleur

Toute clé mal orthographiée ou valeur hors de Couleur déclenche une erreur, sans élargir le type final.

Unions discriminées : éviter le champ optionnel piège

Une erreur fréquente consiste à modéliser un état avec des champs optionnels plutôt qu'avec un vrai discriminant :

// À éviter : les combinaisons impossibles restent typables
type Requete = {
  statut: "chargement" | "succes" | "erreur";
  donnees?: string;
  message?: string;
};

Rien n'empêche ici un objet statut: "erreur" avec des donnees définies et pas de message. La version discriminée élimine ces états invalides :

type Requete =
  | { statut: "chargement" }
  | { statut: "succes"; donnees: string }
  | { statut: "erreur"; message: string };

function afficher(r: Requete) {
  switch (r.statut) {
    case "succes":
      return r.donnees; // narrowing correct
    case "erreur":
      return r.message;
    default:
      return null;
  }
}

Le compilateur narrow automatiquement chaque branche grâce au champ discriminant commun (statut), sans cast ni vérification manuelle.

À retenir

  • satisfies valide un objet contre un type sans en élargir l'inférence, contrairement à une annotation classique (: Type).
  • Une union discriminée avec un champ littéral commun bat systématiquement des champs optionnels pour représenter des états mutuellement exclusifs.
  • Ces deux techniques se combinent bien avec as const pour figer des littéraux avant de les faire vérifier par satisfies.