Retour au cours

frontend / typescript

Enums et const assertions

Leçon 91 exercice

Explication

Ce que vous allez apprendre

  • Déclarer un enum numérique et un enum à valeurs explicites
  • Expliquer pourquoi un enum classique génère du code JavaScript à l'exécution
  • Identifier les limitations d'un enum avec les bundlers modernes (isolatedModules)
  • Construire l'alternative recommandée : un objet as const combiné à typeof/keyof
  • Utiliser as const pour figer un tableau ou un objet littéral en types précis

Dans quel contexte ?

Une équipe migre son projet vers Vite, qui utilise esbuild pour transpiler chaque fichier indépendamment (option isolatedModules). Le build échoue avec une erreur sur un enum Statut { EnAttente, Expediee } utilisé dans plusieurs fichiers : esbuild ne peut pas transpiler un enum sans connaître le contenu des autres fichiers du projet. En remplaçant l'enum par const Statut = { EnAttente: "en_attente", Expediee: "expediee" } as const; associé à type Statut = typeof Statut[keyof typeof Statut];, le projet redevient compatible avec isolatedModules, sans rien perdre en confort d'utilisation.

Nommer un ensemble fini de valeurs

Certaines données ne prennent qu'un nombre limité de valeurs possibles : une direction (haut, bas, gauche, droite), un statut de commande, un code HTTP. Les enums (énumérations) sont l'outil historique de TypeScript pour nommer ce genre d'ensemble fini, en donnant un nom lisible à chaque valeur plutôt que de manipuler des nombres ou des chaînes brutes dans le code.

Prérequis

Cette leçon suppose les literal types et les unions (leçon 6) bien compris : l'alternative moderne aux enums repose entièrement sur la combinaison d'un objet figé et d'une union de types littéraux dérivée.

Un compromis à connaître

Ce que beaucoup découvrent en travaillant avec les enums, c'est qu'ils ne sont pas gratuits : un enum classique génère un véritable objet JavaScript à l'exécution, ce qui ajoute un peu de poids au bundle final et empêche certains outils de compilation modernes, comme esbuild ou swc utilisés par la plupart des bundlers actuels, de fonctionner correctement avec l'option isolatedModules. La variante const enum résout le problème de poids en étant inlinée directement dans le code compilé, mais introduit d'autres limitations techniques.

ApprocheCode généré à l'exécutionCompatible isolatedModules
enum classiqueObjet JavaScript completNon
const enumValeurs inlinéesNon
Objet as const + typeof/keyofObjet simple, minimalOui

Piège fréquent

Utiliser un enum classique dans un projet configuré avec isolatedModules: true (la configuration par défaut de la plupart des bundlers modernes comme Vite) provoque une erreur de build. Préférez le pattern objet as const pour tout nouveau projet.

L'alternative moderne recommandée

La communauté TypeScript s'est largement tournée vers un pattern alternatif : un objet simple figé avec as const, dont on dérive ensuite une union de types littéraux via typeof et keyof. Ce pattern offre le même confort qu'un enum côté code, sans générer de surcoût à l'exécution et sans les limitations de compatibilité — c'est aujourd'hui le choix par défaut recommandé pour les nouveaux projets.

Figer des structures avec as const

Plus largement, as const peut être appliqué à n'importe quel tableau ou objet littéral pour le rendre en lecture seule et transformer chacune de ses valeurs en type littéral précis plutôt qu'en type large — un outil que vous retrouverez régulièrement, notamment dans la leçon sur satisfies.

Commandes & code

Enums et const assertions

ts
// Enum numérique : valeurs auto-incrémentées à partir de 0
enum Direction {
  Haut,   // 0
  Bas,    // 1
  Gauche, // 2
  Droite, // 3
}
function deplacer(direction: Direction) {
  /* ... */
}
deplacer(Direction.Haut);

// Enum avec valeurs explicites
enum CodeHttp {
  Ok = 200,
  NonTrouve = 404,
  ErreurServeur = 500,
}

// Enum de chaînes : plus lisible en debug/logs, pas de valeur "fantôme"
enum StatutCommande {
  EnAttente = "EN_ATTENTE",
  Expediee = "EXPEDIEE",
  Livree = "LIVREE",
}
console.log(StatutCommande.Livree); // "LIVREE", pas un simple nombre

// const enum : inliné à la compilation, aucun objet JS généré (plus performant)
const enum Taille {
  Petit,
  Moyen,
  Grand,
}
let t = Taille.Grand; // compilé en "let t = 2;" — aucune référence à un objet Taille

// Alternative moderne recommandée : union littérale + "as const"
const STATUTS = {
  EN_ATTENTE: "EN_ATTENTE",
  EXPEDIEE: "EXPEDIEE",
  LIVREE: "LIVREE",
} as const;
type StatutCommandeV2 = (typeof STATUTS)[keyof typeof STATUTS];
// "EN_ATTENTE" | "EXPEDIEE" | "LIVREE" — zéro overhead runtime, tree-shakable

// as const sur un tableau : chaque élément devient un type littéral, tuple readonly
const ROLES = ["admin", "editeur", "lecteur"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "editeur" | "lecteur"
// ROLES.push("invite"); // Error : ROLES est readonly

// as const sur un objet complexe
const THEME = {
  couleurs: { primaire: "#0055ff", secondaire: "#ff8800" },
  espacement: 8,
} as const;
// THEME.espacement = 16; // Error : readonly
ApprocheOverhead runtimeTree-shakableRecommandé
enumobjet JS générénoncas legacy
const enumaucun (inliné)ouilimité (incompatible isolatedModules)
union + as constaucunouioui, par défaut

Résumé

  • enum génère un vrai objet JS ; const enum l'inline mais casse avec isolatedModules.
  • Le pattern as const + union littérale est le standard moderne recommandé.
  • as const fige aussi les tableaux et objets en lecture seule, récursivement.

Exercices pratiques

1 disponible
1

Mission : réparer un build esbuild cassé par un enum

Objectif : Diagnostiquer pourquoi un enum classique casse un build isolatedModules, puis le remplacer par le pattern objet as const recommandé.

Contexte

Une équipe migre son build vers Vite (esbuild), qui transpile chaque fichier isolément via l'option isolatedModules. Le build échoue sur enum Statut { EnAttente, Expediee } utilisé dans plusieurs fichiers. Ta mission : comprendre pourquoi, puis migrer vers le pattern objet as const recommandé.

Résoudre l’exercice →