Représenter des valeurs fixes · Gérer les erreurs de manière structurée
Utilisez les flèches, cliquez ou glissez pour naviguer
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
Le problème des valeurs magiques
setStatus('actif') vs setStatus('active') — faute de frappe invisible
Enums numériques puis string
Préférer les string enums pour la lisibilité dans les logs
Enum vs union type
Union pour le simple, enum si besoin d'itérer — règle claire
Erreurs personnalisées
class NotFoundError extends Error — avec données contextuelles + this.name
Namespaces d'erreurs
UserErrors.NotFound, UserErrors.EmailTaken — le pattern de la codebase
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
❌ Avec des strings libres
function setStatus(s: string) {
// s peut ĂŞtre n'importe quoi
}
setStatus('activee'); // passe !
setStatus('ACTIVE'); // passe !
âś… Avec un enum
enum Status {
Actif = 'ACTIF',
Inactif = 'INACTIF',
}
setStatus(Status.Actif); // âś…
setStatus('actif'); // ❌ erreur TS
Un ensemble nommé de valeurs constantes
Numérique
Valeurs : 0, 1, 2…
String
Valeurs : 'ACTIF', 'INACTIF'…
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 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
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); // âś…
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
Quelle syntaxe choisir et pourquoi ?
La règle en une phrase :
union par défaut — enum si besoin d'itérer
Union type
type Theme = 'light' | 'dark';
function setTheme(t: Theme) {}
setTheme('dark'); // âś…
Enum string
enum Theme {
Light = 'light',
Dark = 'dark',
}
setTheme(Theme.Dark); // âś…
Object.values()💡 Si vous avez 2-3 valeurs simples → union type. Si vous devez lister, afficher ou itérer → enum string
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';
}
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 ?
}
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
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.
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)
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
Grouper les erreurs par domaine métier
UserErrors
NotFound, EmailTaken, Unauthorized
OrderErrors
NotFound, AlreadyShipped, EmptyCart
ApiErrors
Timeout, RateLimit, BadGateway
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';
}
}
}
// 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
Namespace TypeScript
Ns.Classenamespace UserErrors { ... }
Module ES (import/export)
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è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 ✅
🔤 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
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 ? 🙋