Type Branding : distinguer les types qui se ressemblent
Le système de types de TypeScript est structurel : deux types avec la même structure sont interchangeables. Mais parfois, un UserId et un ProductId sont tous deux des strings, et les confondre est un bug. Le Type Branding résout ce problème. Chez Eve Media, nous utilisons cette technique pour du code plus sûr.
Le problème
Imaginez deux fonctions : getUser(userId: string) et getProduct(productId: string). Rien n’empêche d’appeler getUser avec un productId. TypeScript ne voit que des strings. Le bug ne sera découvert qu’à l’exécution.
La solution : branding
On crée des types distincts en ajoutant une propriété « fantôme » qui n’existe pas à l’exécution : type UserId = string & { __brand: ‘UserId’ }. Maintenant, TypeScript distingue UserId de ProductId.
Créer des valeurs branded
On ne peut pas juste assigner une string à un UserId. Il faut une fonction de création : function createUserId(id: string): UserId { return id as UserId }. Cette fonction peut aussi valider le format.
Type Flavoring
Une variante plus souple : le flavoring utilise une propriété optionnelle. type UserId = string & { __flavor?: ‘UserId’ }. Les strings simples sont acceptées, mais les types flavored sont distingués entre eux.
Cas d’usage
IDs de différentes entités (UserId, ProductId, OrderId). Unités de mesure (Meters, Kilometers). États validés (ValidatedEmail vs string). Monnaies (EUR, USD). Partout où la confusion serait un bug.
Validation à la frontière
Le branding est particulièrement utile aux frontières du système : API input, lecture base de données. Validez et brandez à l’entrée, utilisez les types branded partout à l’intérieur.
Impact sur le code
Le code devient auto-documenté. sendEmail(email: ValidatedEmail) dit clairement qu’un email validé est requis. Les erreurs sont détectées à la compilation, pas à l’exécution.
Inconvénients
Overhead mental pour l’équipe non familière. Nécessité de créer des fonctions de création/validation. Les casts peuvent contourner la protection si mal utilisés. À utiliser avec discernement.
Helpers et utilities
Créez un type générique pour simplifier : type Brand<T, B> = T & { __brand: B }. type UserId = Brand<string, ‘UserId’>. Des librairies comme ts-brand ou effet proposent des utilities avancés.
Runtime validation
Le branding est une protection compile-time. Pour la validation runtime, combinez avec Zod ou io-ts. Le type branded peut être le output type du schema de validation.
Conclusion
Le Type Branding rend explicites des distinctions implicites. C’est une technique avancée qui, bien utilisée, élimine des catégories entières de bugs. Le typage fort est l’un des plus grands atouts de TypeScript.
Chez Eve Media, nous écrivons du TypeScript robuste. Contactez-nous pour des applications fiables.