Journal

satisfies, const générique et narrowing : les subtilités que beaucoup ratent

Journal du mercredi 19 août 2026, généré le 23 août à 18:56

L'opérateur satisfies résout un vrai dilemme

Avant TypeScript 4.9, on avait deux mauvaises options pour typer un objet littéral : soit l'annoter (au prix de la perte des types précis, élargis à l'interface), soit ne rien annoter (au prix de la vérification). satisfies permet de vérifier la conformité sans élargir le type inféré.

type Couleur = "red" | "green" | "blue";

const palette = {
  ciel: "blue",
  herbe: "green"
} satisfies Record<string, Couleur>;

// palette.ciel est inféré comme "blue", pas comme Couleur
palette.ciel.toUpperCase(); // OK, string literal précis

Avec un simple : Record<string, Couleur>, palette.ciel aurait été typé Couleur au lieu de "blue", et TypeScript aurait accepté n'importe quelle clé sans erreur si vous en aviez tapé une invalide comme "jaune"… en fait non, il l'aurait rejetée aussi, mais vous auriez perdu la précision littérale utile pour l'autocomplétion.

Les paramètres génériques const

Depuis TypeScript 5.0, on peut demander l'inférence la plus stricte possible directement sur un paramètre générique, sans forcer l'appelant à écrire as const partout.

function tuple<const T extends readonly unknown[]>(items: T): T {
  return items;
}

const t = tuple(["a", "b", 1]); 
// t: readonly ["a", "b", 1]  (au lieu de (string | number)[])

C'est particulièrement utile pour les API de configuration ou les builders où l'ordre et les valeurs littérales comptent.

Narrowing par discriminant : attention aux fonctions extraites

Le narrowing sur une union discriminée fonctionne bien tant que la logique reste inline. Dès qu'on extrait une fonction de garde, il faut la typer explicitement avec un type predicate, sinon TypeScript perd le fil.

type Event =
  | { type: "click"; x: number; y: number }
  | { type: "keydown"; key: string };

function isClick(e: Event): e is Extract<Event, { type: "click" }> {
  return e.type === "click";
}

function handle(e: Event) {
  if (isClick(e)) {
    console.log(e.x, e.y); // narrowing correct
  }
}

Sans le e is ..., la fonction retournerait un simple boolean et l'appelant ne bénéficierait d'aucun narrowing après l'appel.

Template literal types pour des chaînes structurées

Les types littéraux de gabarit permettent de valider des formats de chaîne à la compilation, utile pour des routes, des noms d'événements ou des unités.

type Route = `/users/${number}` | `/posts/${string}`;

function navigate(route: Route) { /* ... */ }

navigate("/users/42");   // OK
navigate("/users/abc");  // erreur de compilation

Combinés à infer dans un type conditionnel, ils permettent même d'extraire des segments (paramètres de route, unités) directement au niveau du système de types.