TypeScript : 5 techniques de typage qui évitent des bugs
· 6 min de lecture
Unions discriminées, vérification exhaustive, satisfies, as const et types de marque : cinq techniques pour que le compilateur attrape les bugs à votre place.
En bref
TypeScript relit le code avant qu’il ne tourne. Bien utilisé, il bloque certaines erreurs dès qu’on les écrit. L’article présente cinq façons simples de le laisser trouver les bugs à votre place.
Un mot technique vous échappe ? Le glossaire du développement web l’explique simplement.
Avec TypeScript, le typage n’est pas qu’une documentation : bien utilisé, il rend certains bugs impossibles à écrire. Voici cinq techniques qui évitent des bugs au quotidien, en Angular comme en React ou en NestJS.
1. Les unions discriminées
Plutôt qu’un objet aux champs optionnels qui peuvent se contredire (loading à false mais aucune donnée ?), on décrit chaque état possible :
type RequestState<T> =
| { status: 'loading' }
| { status: 'error'; error: string }
| { status: 'success'; data: T };
function userName(state: RequestState<User>) {
if (state.status === 'success') {
return state.data.name; // data only exists in this branch
}
return null;
}2. La vérification exhaustive
Quand on ajoute un état à l’union, le compilateur doit signaler chaque switch qui l’oublie. Le type never s’en charge :
function label(state: RequestState<unknown>): string {
switch (state.status) {
case 'loading':
return 'Loading…';
case 'error':
return state.error;
case 'success':
return 'Done';
default: {
// Compile error if a case is missing
const unreachable: never = state;
return unreachable;
}
}
}3. satisfies plutôt qu’une annotation
Annoter une constante avec un type large fait perdre l’information précise : avec Record<string, Route>, n’importe quelle clé est acceptée. satisfies vérifie la conformité tout en conservant le type inféré.
type Route = { path: string; auth: boolean };
const routes = {
home: { path: '/', auth: false },
admin: { path: '/admin', auth: true },
} satisfies Record<string, Route>;
routes.admin.path; // OK
routes.adnim; // Error: this key does not exist4. as const pour les listes de valeurs
const PLANS = ['free', 'pro', 'team'] as const;
type Plan = (typeof PLANS)[number]; // 'free' | 'pro' | 'team'
function isPlan(value: string): value is Plan {
return (PLANS as readonly string[]).includes(value);
}Une seule source de vérité : la liste sert à la validation à l’exécution (voir « NestJS : valider les entrées de son API avec les DTO »), et le type en est dérivé. Ajouter une formule met à jour les deux.
5. Les types de marque
Un identifiant d’utilisateur et un identifiant de commande sont tous deux des string : rien n’empêche de les inverser. Un type de marque les rend incompatibles, sans aucun coût à l’exécution :
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };
declare function getUser(id: UserId): Promise<User>;
const orderId = 'order-42' as OrderId;
getUser(orderId); // Error: OrderId is not a UserIdAucune de ces techniques ne demande de librairie. Elles déplacent simplement des erreurs de l’exécution vers la compilation, là où elles coûtent le moins cher.