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 exist

4. 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 UserId

Aucune 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.

Sur le même sujet

  • Angular
  • TypeScript
  • Réactivité

Angular Signals : vers un Angular sans Zone.js

Les signals changent la façon dont Angular met l’écran à jour. Voici comment ils marchent, et comment migrer une application existante.

· 7 min de lecture

  • Performance
  • SEO
  • JavaScript

INP : optimiser la réactivité perçue de son site

L’Interaction to Next Paint a remplacé le FID dans les Core Web Vitals. Comment le mesurer et les techniques concrètes pour l’améliorer.

· 7 min de lecture