Comprendre les enums en TypeScript : guide pratique

Dans cet article

  • Les enums en TypeScript se déclinent en 3 types principaux : numériques, chaînes de caractères et hétérogènes
  • Un enum numérique commence par défaut à 0 et s’incrémente automatiquement, sauf indication contraire
  • Les enums ne sont pas dépréciés dans TypeScript, mais les alternatives comme as const gagnent en popularité depuis TypeScript 5
  • L’utilisation de const enum permet d’éliminer le code généré au runtime et d’alléger le bundle final
  • Pour exporter un enum, la syntaxe export enum suffit dans un module ES classique
  • Les unions de types littéraux constituent une alternative légère recommandée dans de nombreux cas d’usage

Quand j’ai commencé à utiliser TypeScript sur mes projets après des années passées sur PHP et Symfony, les enums faisaient partie des fonctionnalités qui m’ont immédiatement séduit. En tant que développeur habitué aux constantes de classe en PHP 8, j’ai retrouvé dans les enums TypeScript une manière élégante de structurer des valeurs fixes et de renforcer la sécurité du typage. Pourtant, le sujet divise la communauté. Certains les adorent, d’autres les évitent. Dans ce guide, je vous explique tout ce qu’il faut savoir pour les utiliser correctement dans vos projets.

Qu’est-ce qu’un enum en TypeScript ?

Un enum (abréviation de « enumeration ») est une structure de données qui permet de définir un ensemble nommé de constantes. Concrètement, il regroupe des valeurs liées entre elles sous un même nom, ce qui rend le code plus lisible et moins sujet aux erreurs. Contrairement à JavaScript natif qui ne propose pas cette fonctionnalité, TypeScript l’intègre directement dans sa syntaxe.

Prenons un exemple simple. Plutôt que d’écrire des chaînes de caractères en dur dans votre code pour représenter les directions d’un déplacement, vous créez un enum :

enum Direction {
  Haut,
  Bas,
  Gauche,
  Droite
}

Chaque membre reçoit automatiquement une valeur numérique en partant de 0. Ainsi, Direction.Haut vaut 0, Direction.Bas vaut 1, et ainsi de suite. Cette approche évite les « magic numbers » et les fautes de frappe sur des chaînes littérales. La documentation officielle de TypeScript décrit les enums comme un moyen de donner des noms plus explicites à des ensembles de valeurs numériques.

Ce qui distingue les enums TypeScript de simples objets constants, c’est qu’ils existent à la fois au moment de la compilation et au runtime. Le compilateur génère du code JavaScript réel pour les représenter, ce qui a des implications sur la taille du bundle final.

Déclaration d'enums TypeScript dans un éditeur de code en espace de coworking
Déclaration d’enums TypeScript dans un éditeur de code en espace de coworking

Les différents types d’enums

TypeScript propose trois grandes catégories d’enums, chacune adaptée à des besoins spécifiques. Voici un panorama complet pour choisir le bon type selon votre contexte.

Enums numériques

C’est le type par défaut. Les valeurs sont des nombres entiers qui s’incrémentent automatiquement. Vous pouvez aussi définir un point de départ personnalisé :

enum CodeHTTP {
  OK = 200,
  Cree = 201,
  Accepte = 202,
  NonAutorise = 401,
  Interdit = 403,
  NonTrouve = 404
}

Les enums numériques offrent une fonctionnalité appelée reverse mapping : vous pouvez retrouver le nom d’un membre à partir de sa valeur. Par exemple, CodeHTTP[200] retourne la chaîne "OK". C’est pratique pour le débogage, mais cela alourdit le code généré.

Enums de chaînes de caractères

Chaque membre est initialisé avec une valeur littérale de type string. Ce type est très courant dans les projets modernes car les valeurs sont lisibles sans avoir besoin de reverse mapping :

enum Statut {
  Actif = "ACTIF",
  Inactif = "INACTIF",
  EnAttente = "EN_ATTENTE",
  Suspendu = "SUSPENDU"
}

Je recommande les enums de chaînes dans la majorité des cas. Les valeurs apparaissent en clair dans les logs, les appels API et les bases de données, ce qui simplifie considérablement le débogage. Si vous travaillez avec des API REST ou des structures similaires à celles décrites dans notre guide sur l’API Shopify, les enums string facilitent la correspondance avec les contrats d’interface.

Enums hétérogènes

TypeScript autorise le mélange de valeurs numériques et string dans un même enum. La documentation TypeScript précise que cette possibilité existe, mais la déconseille dans la plupart des cas :

enum Melange {
  Non = 0,
  Oui = "OUI"
}

En pratique, je n’ai jamais eu besoin d’un enum hétérogène en production. Si vous vous retrouvez dans cette situation, c’est souvent le signe que votre modèle de données mérite d’être repensé.

Type d’enum Valeurs Reverse mapping Cas d’usage recommandé
Numérique Nombres entiers auto-incrémentés Oui Flags binaires, index ordonnés
Chaîne (string) Chaînes littérales explicites Non Statuts, rôles, clés API
Hétérogène Mix nombres et chaînes Partiel À éviter en production
Const enum Nombres ou chaînes Non (inliné) Optimisation de bundle

Syntaxe et exemples pratiques

Voyons maintenant des cas concrets que j’utilise régulièrement dans mes projets. La syntaxe de base est simple, mais quelques subtilités méritent votre attention.

Déclaration et utilisation basique

enum Role {
  Administrateur = "ADMIN",
  Editeur = "EDITEUR",
  Lecteur = "LECTEUR"
}

function peutModifier(role: Role): boolean {
  return role === Role.Administrateur || role === Role.Editeur;
}

const monRole: Role = Role.Editeur;
console.log(peutModifier(monRole)); // true

L’enum Role sert ici de type et de valeur simultanément. La fonction peutModifier accepte uniquement un paramètre de type Role ; toute autre valeur provoque une erreur de compilation. C’est cette sécurité qui fait la force des enums par rapport à de simples chaînes.

Utilisation avec un switch-case

Les enums se marient parfaitement avec les structures conditionnelles. Si vous êtes familier avec le switch case en PHP, le principe est identique en TypeScript :

enum TypeNotification {
  Email = "EMAIL",
  SMS = "SMS",
  Push = "PUSH"
}

function envoyer(type: TypeNotification, message: string): void {
  switch (type) {
    case TypeNotification.Email:
      envoyerEmail(message);
      break;
    case TypeNotification.SMS:
      envoyerSMS(message);
      break;
    case TypeNotification.Push:
      envoyerPush(message);
      break;
  }
}

TypeScript vérifie l’exhaustivité du switch : si vous ajoutez un nouveau membre à l’enum sans traiter le cas correspondant, le compilateur vous avertit. Cette garantie réduit considérablement les bugs liés à des cas oubliés.

Const enum pour optimiser le bundle

Le mot-clé const devant un enum change radicalement son comportement à la compilation :

const enum Couleur {
  Rouge = "#FF0000",
  Vert = "#00FF00",
  Bleu = "#0000FF"
}

const maCouleur = Couleur.Rouge;
// Compilé en : const maCouleur = "#FF0000";

Avec un const enum, le compilateur remplace chaque référence par sa valeur littérale. Aucun objet JavaScript n’est généré au runtime. Le gain est significatif sur les gros projets où des dizaines d’enums alourdissent le bundle. Pour les développeurs soucieux de la performance, comme dans le contexte d’un déploiement Docker optimisé, chaque kilo-octet économisé compte.

Planification d'architecture logicielle avec diagrammes sur tableau blanc
Planification d’architecture logicielle avec diagrammes sur tableau blanc

Exporter et organiser ses enums

Dans un projet structuré, vous aurez besoin de partager vos enums entre plusieurs fichiers. La syntaxe d’export est classique en TypeScript :

// fichier: src/enums/statut.ts
export enum StatutCommande {
  EnCours = "EN_COURS",
  Expediee = "EXPEDIEE",
  Livree = "LIVREE",
  Annulee = "ANNULEE"
}

// fichier: src/services/commande.service.ts
import { StatutCommande } from "../enums/statut";

function estFinalisee(statut: StatutCommande): boolean {
  return statut === StatutCommande.Livree || statut === StatutCommande.Annulee;
}

Je recommande de centraliser vos enums dans un dossier dédié (src/enums/ ou src/types/) et de les regrouper par domaine métier. Un fichier barrel (index.ts) facilite les imports :

// fichier: src/enums/index.ts
export { StatutCommande } from "./statut";
export { Role } from "./role";
export { TypeNotification } from "./notification";

Cette organisation est similaire à ce que l’on pratique en PHP avec les namespaces. Si vous venez du monde PHP 8 comme moi, vous retrouverez vos repères assez vite. La discipline dans l’organisation des fichiers évite les imports circulaires et facilite la maintenance à long terme.

Enum vs type union : quelle alternative choisir ?

C’est le débat qui agite la communauté TypeScript depuis plusieurs années. Les unions de types littéraux et la syntaxe as const offrent des alternatives sérieuses aux enums classiques. Voici une comparaison honnête.

L’approche union de types

// Alternative sans enum
type Statut = "ACTIF" | "INACTIF" | "EN_ATTENTE";

function traiter(statut: Statut): void {
  // TypeScript vérifie que statut est bien l'une des trois valeurs
}

Cette approche est purement typale : elle n’existe qu’à la compilation et ne génère aucun code JavaScript. C’est son avantage principal sur les enums classiques.

L’approche as const

const STATUTS = {
  Actif: "ACTIF",
  Inactif: "INACTIF",
  EnAttente: "EN_ATTENTE"
} as const;

type Statut = typeof STATUTS[keyof typeof STATUTS];
// Équivalent à : type Statut = "ACTIF" | "INACTIF" | "EN_ATTENTE"

Avec as const, vous obtenez un objet immutable dont les valeurs sont inférées comme types littéraux. Vous conservez l’objet au runtime (utile pour itérer sur les valeurs) sans le surcoût des enums.

Critère Enum classique Union de types as const
Code généré au runtime Oui (objet JS) Non Oui (objet simple)
Itération sur les valeurs Oui Non Oui
Reverse mapping Oui (numérique) Non Possible manuellement
Taille du bundle Plus lourde Zéro impact Légère
Compatibilité isolatedModules Problèmes possibles Aucun souci Aucun souci
Lisibilité Excellente Bonne Moyenne

Mon avis personnel : si votre projet est récent et que vous n’avez pas de contrainte particulière, les unions de types suffisent pour la majorité des cas simples. En revanche, dès que vous avez besoin d’itérer sur les valeurs ou de les utiliser comme objets au runtime, les enums ou as const restent pertinents.

Les enums sont-ils dépréciés en TypeScript ?

La question revient régulièrement sur les forums et les réseaux. La réponse courte : non, les enums ne sont pas dépréciés. Ils font toujours partie intégrante de la spécification TypeScript et continuent d’être maintenus par l’équipe Microsoft.

Ce qui alimente la confusion, c’est la montée en puissance des alternatives (as const, unions de types) et les articles provocateurs comme « TypeScript Enums are Terrible ». Ces critiques pointent des problèmes réels :

  • Les enums numériques acceptent n’importe quel nombre comme valeur, ce qui casse la sécurité du typage
  • Le code JavaScript généré est plus verbeux qu’un simple objet
  • Les const enums posent des problèmes avec isolatedModules et certains bundlers
  • Le comportement de reverse mapping peut surprendre les développeurs débutants

Cependant, ces limitations ne justifient pas d’abandonner les enums. L’équipe TypeScript a d’ailleurs amélioré leur comportement dans les versions récentes. La version 5.0 a notamment corrigé certains problèmes de typage sur les enums numériques. La communauté reste active autour de cette fonctionnalité, comme le montrent les discussions sur le dépôt GitHub officiel de TypeScript.

En résumé : utilisez les enums quand ils apportent de la valeur, optez pour les alternatives quand elles sont plus adaptées. Ce n’est pas un choix binaire.

Bonnes pratiques et pièges à éviter

Après plusieurs années à utiliser les enums en production, voici les règles que j’applique systématiquement dans mes projets.

Privilégiez les enums string

Les enums de chaînes évitent le piège du reverse mapping implicite des enums numériques. Ils produisent des valeurs lisibles dans les logs et les payloads JSON. C’est particulièrement important lorsque vous travaillez avec des API REST ou des bases de données relationnelles, où les valeurs doivent être compréhensibles sans contexte supplémentaire. Si vous manipulez des données au format similaire à un format SQL, des valeurs string explicites facilitent les requêtes.

Nommez vos enums au singulier

// Correct
enum Role { Admin, Editeur, Lecteur }

// À éviter
enum Roles { Admin, Editeur, Lecteur }

Un enum représente un type unique ; le singulier reflète mieux cette sémantique. Un tableau de rôles sera typé Role[], ce qui reste parfaitement lisible.

Utilisez const enum avec précaution

Les const enum sont tentants pour optimiser le bundle, mais ils posent des problèmes dans certains contextes :

  • Incompatibilité avec isolatedModules (requis par Babel, esbuild, SWC)
  • Impossibilité d’itérer sur les membres au runtime
  • Comportement différent selon que le code est compilé comme library ou application

Si votre projet utilise un bundler moderne, préférez un enum classique ou as const plutôt que const enum.

Ne mélangez pas les types de valeurs

Les enums hétérogènes (mix de nombres et de chaînes) sont une source de confusion. Choisissez un type et restez cohérent. Si vous avez besoin de plusieurs types de valeurs pour une même entité, créez plusieurs enums distincts ou utilisez un objet typé.

Comparaison de deux approches de typage sur un écran partagé
Comparaison de deux approches de typage sur un écran partagé

Évitez les enums numériques implicites pour les valeurs métier

// Dangereux : l'ajout d'un membre au milieu décale toutes les valeurs
enum Priorite {
  Basse,    // 0
  Moyenne,  // 1
  Haute,    // 2
  Critique  // 3
}

// Plus sûr : valeurs explicites
enum Priorite {
  Basse = 1,
  Moyenne = 2,
  Haute = 3,
  Critique = 4
}

Si vos valeurs numériques sont stockées en base de données, un réordonnancement accidentel des membres peut corrompre vos données. Assignez toujours des valeurs explicites dans ce cas.

Cas d’usage concrets en projet

Pour illustrer l’utilité des enums dans des contextes réels, voici trois scénarios que je rencontre fréquemment.

Gestion des rôles utilisateurs

export enum RoleUtilisateur {
  SuperAdmin = "SUPER_ADMIN",
  Admin = "ADMIN",
  Moderateur = "MODERATEUR",
  Membre = "MEMBRE",
  Invite = "INVITE"
}

const PERMISSIONS: Record<RoleUtilisateur, string[]> = {
  [RoleUtilisateur.SuperAdmin]: ["lire", "ecrire", "supprimer", "gerer_utilisateurs"],
  [RoleUtilisateur.Admin]: ["lire", "ecrire", "supprimer"],
  [RoleUtilisateur.Moderateur]: ["lire", "ecrire"],
  [RoleUtilisateur.Membre]: ["lire"],
  [RoleUtilisateur.Invite]: []
};

Ce pattern associe un enum à un objet de permissions indexé. L’ajout d’un nouveau rôle dans l’enum force le développeur à définir ses permissions, car TypeScript signale l’absence de la clé dans le Record. Cette approche est particulièrement utile dans un contexte DevOps où la sécurité des accès est critique.

Machine à états pour un workflow

export enum StatutArticle {
  Brouillon = "BROUILLON",
  EnRevision = "EN_REVISION",
  Approuve = "APPROUVE",
  Publie = "PUBLIE",
  Archive = "ARCHIVE"
}

const TRANSITIONS: Record<StatutArticle, StatutArticle[]> = {
  [StatutArticle.Brouillon]: [StatutArticle.EnRevision],
  [StatutArticle.EnRevision]: [StatutArticle.Brouillon, StatutArticle.Approuve],
  [StatutArticle.Approuve]: [StatutArticle.Publie, StatutArticle.EnRevision],
  [StatutArticle.Publie]: [StatutArticle.Archive],
  [StatutArticle.Archive]: []
};

function peutTransiter(actuel: StatutArticle, cible: StatutArticle): boolean {
  return TRANSITIONS[actuel].includes(cible);
}

Les enums modélisent naturellement les états finis d’un workflow. Combinés avec un objet de transitions, ils forment une machine à états simple et maintenable. C’est un pattern que j’utilise aussi bien côté frontend (React) que côté backend (Node.js).

Configuration d’environnements

export enum Environnement {
  Developpement = "development",
  Test = "test",
  Recette = "staging",
  Production = "production"
}

function getBaseUrl(env: Environnement): string {
  switch (env) {
    case Environnement.Developpement:
      return "http://localhost:3000";
    case Environnement.Test:
      return "http://test.example.com";
    case Environnement.Recette:
      return "https://staging.example.com";
    case Environnement.Production:
      return "https://api.example.com";
  }
}

Ce pattern centralise les configurations par environnement avec une sécurité de type complète. L’ajout d’un nouvel environnement provoque une erreur si le switch n’est pas mis à jour. C’est une technique particulièrement pertinente si vous mettez en place une ingénierie DevOps rigoureuse avec des pipelines de déploiement multi-environnements.

Pour les développeurs qui souhaitent approfondir leurs compétences en TypeScript et dans l’écosystème JavaScript moderne, une formation spécialisée en développement web peut constituer un excellent accélérateur.

À retenir

  • Préférez les enums de chaînes aux enums numériques pour éviter les pièges du reverse mapping
  • Utilisez as const ou les unions de types pour les cas simples sans besoin d’itération au runtime
  • Assignez toujours des valeurs explicites aux enums numériques stockés en base de données
  • Centralisez vos enums dans un dossier src/enums/ avec un fichier barrel index.ts
  • Activez le contrôle d’exhaustivité du switch pour ne jamais oublier un cas

Questions fréquentes


Qu’est-ce qu’un enum en TypeScript ?

Un enum (enumeration) est une structure TypeScript qui définit un ensemble nommé de constantes. Il permet de regrouper des valeurs liées sous un même type, renforçant la lisibilité du code et la sécurité du typage. Un enum peut contenir des valeurs numériques ou des chaînes de caractères.


Comment illustrer un enum avec un exemple concret ?

Un exemple classique est la gestion des statuts d’une commande : enum StatutCommande { EnCours = "EN_COURS", Expediee = "EXPEDIEE", Livree = "LIVREE" }. Ce code crée un type réutilisable qui interdit toute valeur non prévue, éliminant les erreurs liées aux chaînes mal orthographiées.


Pourquoi certains développeurs évitent-ils les enums en TypeScript ?

Les critiques portent sur le code JavaScript généré (plus verbeux qu’un objet simple), les problèmes de compatibilité avec isolatedModules, et le fait que les enums numériques acceptent n’importe quel nombre. Les alternatives comme as const et les unions de types résolvent ces limitations dans de nombreux cas.


Les enums sont-ils dépréciés dans TypeScript ?

Non, les enums ne sont pas dépréciés. Ils restent pleinement supportés et maintenus par l’équipe TypeScript chez Microsoft. La confusion vient de la popularité croissante des alternatives, mais les enums continuent d’être améliorés à chaque version majeure du langage.


Quand utiliser un enum plutôt qu’un type union en TypeScript ?

Utilisez un enum quand vous avez besoin d’itérer sur les valeurs au runtime, de reverse mapping, ou de regrouper des constantes dans un objet nommé. Optez pour un type union (type Statut = "actif" | "inactif") quand vous souhaitez zéro impact sur le bundle et une syntaxe plus légère.


Comment exporter un enum en TypeScript ?

Il suffit d’ajouter le mot-clé export devant la déclaration : export enum MonEnum { ... }. L’import se fait ensuite avec la syntaxe classique ES Modules : import { MonEnum } from "./chemin". Pour les projets volumineux, un fichier barrel (index.ts) centralise tous les exports.


Damien Roux
Damien Roux

Ingénieur système et expert hébergement web. Fondateur de web-city.fr, il partage guides pratiques, comparatifs objectifs et outils gratuits pour choisir le bon hébergeur et créer son site WordPress.

Scroll to Top