Kodokon kodokon.com

Template literal types : des chaînes calculées à la compilation

Exploitez les template literal types pour générer des unions par produit cartésien et parser des littéraux de chaîne avec infer.

9 min · 3 questions

Ouvrir cette leçon dans Kodokon

Les template literal types appliquent la syntaxe des littéraux de gabarit au monde des types. Interpolez une union et le compilateur calcule le produit cartésien : chaque combinaison possible devient un membre du résultat. C'est l'outil qui permet de typer des clés d'événements, des routes ou des tokens de design system au caractère près.

TYPESCRIPT
type Lang = "fr" | "en";
type Theme = "light" | "dark";

type ThemeKey = `${Lang}-${Theme}`;
// "fr-light" | "fr-dark" | "en-light" | "en-dark"
Deux unions interpolées : produit cartésien de 4 membres.

La vraie puissance apparaît combinée à infer : un template literal type devient un motif de filtrage sur les chaînes. Le compilateur découpe le littéral d'entrée pour faire correspondre chaque segment ; face à plusieurs découpages possibles, la première occurrence du séparateur gagne. Ajoutez la récursivité et vous obtenez de véritables parseurs évalués à la compilation.

TYPESCRIPT
type Split<S extends string, Sep extends string> =
  S extends `${infer Head}${Sep}${infer Rest}`
    ? [Head, ...Split<Rest, Sep>]
    : [S];

type Parts = Split<"a.b.c", ".">;
// ["a", "b", "c"]
Un split récursif entièrement statique.

Ce motif brille pour extraire les paramètres d'une route au format Express. Chaque appel récursif consomme un segment :param, et l'union finale énumère tous les noms de paramètres, exploitables ensuite dans un Record pour typer la signature du handler.

TYPESCRIPT
type Params<Path extends string> =
  Path extends `${string}:${infer P}/${infer Rest}`
    ? P | Params<Rest>
    : Path extends `${string}:${infer P}`
      ? P
      : never;

type RouteParams = Params<"/users/:id/posts/:postId">;
// "id" | "postId"
Extraction des paramètres d'une route à la compilation.

TypeScript fournit quatre utilitaires intrinsèques - Uppercase, Lowercase, Capitalize, Uncapitalize - implémentés directement dans le compilateur (mot-clé intrinsic), donc impossibles à réécrire soi-même. Autre pattern de production : string & {}. Dans une union, string absorberait les littéraux et l'éditeur perdrait l'auto-complétion ; l'intersection avec {} crée un type équivalent mais distinct, que le compilateur ne réduit pas.

TYPESCRIPT
type Method = "get" | "post";
type Handler = `on${Capitalize<Method>}`;
// "onGet" | "onPost"

type KnownColor = "red" | "green" | "blue";
type Color = KnownColor | (string & {});

declare function paint(color: Color): void;

paint("red");     // suggere par l'auto-completion
paint("#ff8800"); // accepte egalement
Intrinsèques et astuce string & {} pour l'auto-complétion.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Si A compte 3 membres et B en compte 4, combien de membres contient ${A}-${B} ?
    • 7
    • 12
    • 1
    • 4
  2. Que vaut Capitalize appliqué au littéral « hello world » ?
    • « Hello World »
    • « Hello world »
    • « HELLO WORLD »
  3. Pourquoi écrire KnownColor | (string & {}) plutôt que KnownColor | string ?
    • Pour interdire les chaînes hors de KnownColor
    • Pour empêcher string d'absorber les littéraux et conserver l'auto-complétion
    • Pour améliorer les performances du compilateur
    • Les deux formes sont strictement équivalentes