TypeScript — Enums & Erreurs

Représenter des valeurs fixes · Gérer les erreurs de manière structurée

Utilisez les flèches, cliquez ou glissez pour naviguer

Objectifs de la leçon

1. Comprendre les enums numériques et string

Deux syntaxes, un seul objectif : nommer les valeurs fixes

2. Savoir quand utiliser un enum vs un union type

La règle simple : union par défaut, enum si besoin d'itérer

3. Créer des classes d'erreur personnalisées

class NotFoundError extends Error avec données contextuelles

4. Grouper les erreurs par domaine avec les namespaces

UserErrors.NotFound · UserErrors.EmailTaken

Plan du cours

1

Le problème des valeurs magiques

setStatus('actif') vs setStatus('active') — faute de frappe invisible

2

Enums numériques puis string

Préférer les string enums pour la lisibilité dans les logs

3

Enum vs union type

Union pour le simple, enum si besoin d'itérer — règle claire

4

Erreurs personnalisées

class NotFoundError extends Error — avec données contextuelles + this.name

5

Namespaces d'erreurs

UserErrors.NotFound, UserErrors.EmailTaken — le pattern de la codebase

Le problème des valeurs magiques

Des chaînes de caractères libres… c'est dangereux

Pouvez-vous repérer le bug ?

setStatus('actif');     // âś… ça fonctionne

setStatus('active');    // ❌ bug silencieux !

setStatus('Actif');     // ❌ majuscule oubliĂ©e

Pourquoi c'est problématique

❌ Avec des strings libres

function setStatus(s: string) {

// s peut ĂŞtre n'importe quoi

}

setStatus('activee'); // passe !

setStatus('ACTIVE');  // passe !

  • ⚠️ Aucune autocomplĂ©tion
  • ⚠️ Aucune vĂ©rification Ă  la compilation
  • ⚠️ Bug invisible jusqu'en production

âś… Avec un enum

enum Status {

Actif = 'ACTIF',

Inactif = 'INACTIF',

}

setStatus(Status.Actif); // âś…

setStatus('actif'); // ❌ erreur TS

  • âś… AutocomplĂ©tion IDE
  • âś… Erreur Ă  la compilation
  • âś… Impossible de se tromper

Les Enums

Un ensemble nommé de valeurs constantes

Numérique

Valeurs : 0, 1, 2…

String

Valeurs : 'ACTIF', 'INACTIF'…

Enum numérique

enum Direction {

Haut,    // 0

Bas,     // 1

Gauche// 2

Droite  // 3

}

console.log(Direction.Haut);  // 0

console.log(Direction.Droite); // 3

⚠️ Le piège des enums numériques

console.log(Direction[0]); // "Haut" — reverse mapping

Dans les logs : status: 2 ne dit rien Ă  personne

Enum string — la version préférée

enum Status {

Actif   = 'ACTIF',

Inactif = 'INACTIF',

Banni   = 'BANNI',

}

console.log(Status.Actif);   // "ACTIF"

console.log(Status.Inactif); // "INACTIF"

✅ Pourquoi les string enums sont préférables

đź“‹ status: "ACTIF" dans les logs = lisible

🗄️ Stocké tel quel en base de données

🔍 Facile à déboguer (valeur explicite)

đź”— Pas de reverse mapping ambigu

Utiliser un enum — exemple complet

enum OrderStatus {

EnAttente  = 'EN_ATTENTE',

Confirmee  = 'CONFIRMEE',

Expediee   = 'EXPEDIEE',

Livree     = 'LIVREE',

Annulee    = 'ANNULEE',

}

interface Order {

id: number;

status: OrderStatus;

}

function updateStatus(order: Order, newStatus: OrderStatus): void {

// impossible de passer une string arbitraire ici

order.status = newStatus;

}

updateStatus(order, OrderStatus.Expediee); // âś…

Itérer sur un enum

C'est une des vraies raisons d'utiliser un enum plutĂ´t qu'un union type

enum Role {

Admin   = 'ADMIN',

Editeur = 'EDITEUR',

Lecteur = 'LECTEUR',

}

// Afficher tous les rĂ´les disponibles dans un select

const tousLesRoles = Object.values(Role);

console.log(tousLesRoles);

// ["ADMIN", "EDITEUR", "LECTEUR"]

✅ Utilisation pratique : remplir un <select> dynamiquement, générer des cas de test, valider des entrées

Enum vs Union type

Quelle syntaxe choisir et pourquoi ?

La règle en une phrase :

union par défaut — enum si besoin d'itérer

Comparaison cĂ´te Ă  cĂ´te

Union type

type Theme = 'light' | 'dark';

function setTheme(t: Theme) {}

setTheme('dark'); // âś…

  • âś… Syntaxe lĂ©gère
  • âś… Pas de code JS gĂ©nĂ©rĂ©
  • ❌ Pas d'itĂ©ration possible

Enum string

enum Theme {

Light = 'light',

Dark  = 'dark',

}

setTheme(Theme.Dark); // âś…

  • âś… AutocomplĂ©tion riche
  • âś… ItĂ©rable avec Object.values()
  • ⚠️ Du code JS est gĂ©nĂ©rĂ©

💡 Si vous avez 2-3 valeurs simples → union type. Si vous devez lister, afficher ou itérer → enum string

Exemples concrets : quoi choisir ?

🔵

Union type — type Direction = 'left' | 'right'

2 valeurs, pas besoin d'itérer, simple prop de composant React

🟣

Enum — statuts de commande, rôles utilisateur, catégories de produit

4+ valeurs, affichage dans un select, switch exhaustif en back-end

đź”¶

Switch exhaustif avec enum

switch (order.status) {

case OrderStatus.EnAttente: return 'orange';

case OrderStatus.Livree:    return 'green';

default: return 'gray';

}

Classes d'erreur personnalisées

Parce que new Error('not found') ne suffit pas

Que faire avec cette erreur ?

catch (e) {

console.log(e.message); // "not found" — et alors ?

// Quel user ? Quelle ressource ? Quel ID ?

}

Créer une erreur personnalisée

class NotFoundError extends Error {

public readonly resourceId: string;

constructor(resourceId: string, message?: string) {

super(message ?? `Ressource ${resourceId} introuvable`);

this.name = 'NotFoundError';  // ⬅️ ESSENTIEL

this.resourceId = resourceId;

}

}

// Utilisation

throw new NotFoundError('user-42');

extends Error

Hérite de Error

stack trace inclus

this.name

Nom de la classe

pour instanceof

resourceId

Donnée contextuelle

info utile au debug

Pourquoi this.name est obligatoire

❌ Sans this.name

catch (e) {

console.log(e.name);

// "Error" — générique !

e instanceof NotFoundError

// peut retourner false !

}

âś… Avec this.name

catch (e) {

console.log(e.name);

// "NotFoundError" âś…

e instanceof NotFoundError

// true âś…

}

⚙️ Pourquoi ce comportement existe-t-il ?

En JavaScript, quand on étend une classe native (Error, Array…), le prototype peut être mal configuré. this.name corrige manuellement le nom pour les outils de debug.

Erreurs avec données contextuelles

class ValidationError extends Error {

public readonly field: string;

public readonly value: unknown;

constructor(field: string, value: unknown, message: string) {

super(message);

this.name = 'ValidationError';

this.field = field;

this.value = value;

}

}

throw new ValidationError('email', 'pas-un-email', 'Format invalide');

âś… Dans le catch, vous savez exactement :

Quel champ a échoué (field), quelle valeur a été soumise (value), et pourquoi (message)

Attraper et discriminer les erreurs

try {

await fetchUser(userId);

} catch (e) {

if (e instanceof NotFoundError) {

showNotFoundPage(e.resourceId); // ✅ données contextuelles

} else if (e instanceof ValidationError) {

highlightField(e.field, e.value); // âś… champ exact

} else {

showGenericError();              // fallback

}

}

✅ TypeScript sait le type de e dans chaque branche — accès aux propriétés sans cast

Namespaces d'erreurs

Grouper les erreurs par domaine métier

UserErrors

NotFound, EmailTaken, Unauthorized

OrderErrors

NotFound, AlreadyShipped, EmptyCart

ApiErrors

Timeout, RateLimit, BadGateway

Syntaxe du namespace TypeScript

namespace UserErrors {

export class NotFound extends Error {

constructor(public userId: string) {

super(`Utilisateur ${userId} introuvable`);

this.name = 'UserErrors.NotFound';

}

}

export class EmailTaken extends Error {

constructor(public email: string) {

super(`L'email ${email} est déjà utilisé`);

this.name = 'UserErrors.EmailTaken';

}

}

}

Utiliser les namespaces d'erreurs

// Lancer une erreur

throw new UserErrors.NotFound('user-42');

throw new UserErrors.EmailTaken('john@example.com');

// Attraper une erreur

try {

await registerUser(email);

} catch (e) {

if (e instanceof UserErrors.EmailTaken) {

showError(`${e.email} est déjà pris`);

}

}

✅ Le namespace sert de préfixe visuel : UserErrors.NotFound — on sait immédiatement à quel domaine appartient l'erreur

Attention : Namespaces ≠ Modules ES

Namespace TypeScript

  • âś… Groupement logique interne
  • âś… Compile en objet JS
  • âś… Accessible via Ns.Classe
  • ⚠️ TypeScript seulement

namespace UserErrors { ... }

Module ES (import/export)

  • âś… Fichiers sĂ©parĂ©s
  • âś… Standard JavaScript (ESM)
  • âś… Tree-shakeable
  • âś… RecommandĂ© pour tout le reste

export { UserErrors } from './errors'

💡 Dans la codebase Bootcode, les namespaces sont utilisés à l'intérieur d'un fichier — pas pour remplacer les modules

Pièges courants à éviter

❌ Piège 1 : Oublier this.name dans les erreurs

// Sans this.name

e.name // "Error" — inutile

// Avec this.name

e.name // "NotFoundError" âś…

❌ Piège 2 : Confondre enum et union type

Union = léger, pas de code JS généré. Enum = itérable, code généré. Ne pas choisir enum "parce que ça ressemble à une constante"

❌ Piège 3 : Enum numérique → illisible dans les logs

status: 2  // C'est quoi 2 ? Actif ? Banni ?

status: "ACTIF" // Clair immédiatement ✅

Points clés à retenir

🔤 Les string enums sont préférables aux numériques

'ACTIVE' est plus lisible que 2 dans les logs et en base

⚖️ Union par défaut, enum si besoin d'itérer

Un union type ne génère pas de code JS — préférez-le si vous n'avez pas besoin de Object.values()

🏗️ Les erreurs personnalisées portent des informations utiles

Pas juste un message — userId, field, value… les données qui aident à corriger

🏷️ Toujours définir this.name dans le constructeur d'erreur

Indispensable pour que instanceof fonctionne correctement

🗂️ Les namespaces organisent les erreurs par domaine

UserErrors.NotFound — le pattern exact de la codebase Bootcode

Récapitulatif

Enums

🔤 String enum = lisible dans les logs

🔢 Numérique = à éviter (illisible)

📋 Itérable avec Object.values()

Union type

⚡ Léger, pas de code JS généré

✅ Par défaut pour 2-3 valeurs

❌ Pas itérable

Erreurs personnalisées

🏗️ extends Error + données

🏷️ this.name TOUJOURS

🎯 instanceof pour discriminer

Namespaces

🗂️ Groupement par domaine métier

📌 UserErrors.NotFound

⚠️ ≠ modules ES (import/export)

Des questions ? 🙋