Tous les devs TypeScript connaissent les bases : interfaces, types union, génériques. Mais ce sont les patterns avancés (souvent peu connus) qui font la différence entre un code TypeScript verbeux et frustrant, et un code qui rend les bugs impossibles à compiler. Cet article compile 10 patterns qu'on utilise quotidiennement chez Krealabs sur nos projets Next.js et React Native, avec exemples concrets. Long mais utile à garder en bookmark.
011. Branded types : empêcher les confusions d'IDs
Le problème : `function deleteUser(userId: string)` accepte n'importe quel string, même un `projectId` ou un `email`. Branded types rendent les IDs typiquement incompatibles entre eux. Plus jamais de bug "j'ai passé un postId à une fonction qui attend un userId".
type Brand<K, T> = K & { __brand: T };
type UserId = Brand<string, 'UserId'>;
type ProjectId = Brand<string, 'ProjectId'>;
function deleteUser(id: UserId) { /* ... */ }
const projectId = 'proj_123' as ProjectId;
deleteUser(projectId); // ❌ Error: ProjectId not assignable to UserId
const userId = 'user_456' as UserId;
deleteUser(userId); // ✅ OK022. Exhaustive switch avec never
Quand on a un type union (`'pending' | 'paid' | 'failed'`) et qu'on switch dessus, TS peut nous forcer à gérer TOUS les cas. Si on ajoute `'cancelled'` au type plus tard, le compilateur nous le signale partout.
type Status = 'pending' | 'paid' | 'failed';
function statusLabel(s: Status): string {
switch (s) {
case 'pending': return 'En attente';
case 'paid': return 'Payé';
case 'failed': return 'Échec';
default:
const _exhaustive: never = s;
throw new Error(`Status non géré: ${_exhaustive}`);
}
}
// Si on ajoute 'cancelled' au type Status :
// → erreur de compilation sur la ligne never car 'cancelled' échappe au switch033. Type guards custom pour narrowing fiable
Pour distinguer des types union au runtime, écrire des type guards user-defined avec `is`. Plus sûr que `typeof` ou `instanceof` qui ont des edge cases.
interface User { type: 'user'; id: string; email: string }
interface Org { type: 'org'; id: string; name: string }
type Entity = User | Org;
function isUser(e: Entity): e is User {
return e.type === 'user';
}
function sendEmail(e: Entity) {
if (isUser(e)) {
// TS sait que e est User ici, accès à e.email autorisé
sendTo(e.email);
} else {
// e est Org ici
sendToOrgAdmin(e.id);
}
}044. Template literal types pour URLs typées
Les template literal types (TS 4.1+) permettent de typer les patterns de strings. Cas d'usage idéal : URLs API typées, slugs, événements analytics.
type ApiPath = `/api/${'users' | 'projects' | 'invoices'}/${string}`;
function apiCall(path: ApiPath) { /* ... */ }
apiCall('/api/users/123'); // ✅
apiCall('/api/foo/bar'); // ❌ 'foo' not in union
apiCall('/users/123'); // ❌ doesn't start with /api/055. const assertions pour figer les literals
Sans `as const`, TS infère le type le plus large (`string`, `number`). Avec, il infère le type le plus étroit (le literal exact). Utile pour les configs, les arrays de constantes, les action types Redux.
const ROLES = ['admin', 'editor', 'viewer']; // type string[]
const ROLES_STRICT = ['admin', 'editor', 'viewer'] as const;
// type readonly ['admin', 'editor', 'viewer']
type Role = (typeof ROLES_STRICT)[number];
// 'admin' | 'editor' | 'viewer'
// On peut maintenant utiliser Role partout066. Utility types : Pick, Omit, Partial, Required
Le quartet de base à maîtriser. Ils évitent de redéfinir des types à la main.
interface User {
id: string;
email: string;
name: string;
password: string;
createdAt: Date;
}
// API publique : sans password
type PublicUser = Omit<User, 'password'>;
// Formulaire de signup : tout sauf id/createdAt (auto-générés)
type SignupForm = Omit<User, 'id' | 'createdAt'>;
// Update partiel : tous les champs optionnels
type UserUpdate = Partial<Omit<User, 'id'>>;
// Filtre minimal : juste id et email
type UserRef = Pick<User, 'id' | 'email'>;077. ReturnType + Parameters pour typer indirectement
Plutôt que dupliquer un type, dériver depuis la signature de fonction. Pratique pour rester DRY quand la source de vérité est la fonction (ex: tRPC, Prisma).
async function getUser(id: string) {
return { id, name: 'Alice', email: 'a@b.com' };
}
type User = Awaited<ReturnType<typeof getUser>>;
// User = { id: string; name: string; email: string }
type GetUserArgs = Parameters<typeof getUser>;
// [id: string]088. Discriminated unions pour les états React
Pour modéliser des états mutuellement exclusifs (loading / success / error), utiliser un champ discriminant qui rend le type-narrowing automatique.
type FetchState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
function render<T>(state: FetchState<T>) {
if (state.status === 'success') {
// TS sait que state.data existe ici
return <Display data={state.data} />;
}
if (state.status === 'error') {
// state.error existe
return <Error msg={state.error.message} />;
}
return <Spinner />;
}099. satisfies pour valider sans élargir le type
L'opérateur `satisfies` (TS 4.9+) vérifie qu'une expression matche un type, MAIS garde l'inférence la plus étroite. Très utile pour les config objects.
type Color = 'red' | 'green' | 'blue';
type Config = Record<string, { color: Color; weight: number }>;
const config = {
primary: { color: 'red', weight: 1 },
secondary: { color: 'green', weight: 0.5 },
} satisfies Config;
// TS sait que config.primary.color est exactement 'red' (pas 'Color')
// Et que les clés sont exactement 'primary' | 'secondary' (pas string)
// Tout en garantissant que la structure matche Config1010. Strict null checks : la base non négociable
Activer `strict: true` dans tsconfig.json. Ça active strictNullChecks (les `null`/`undefined` ne sont plus assignables à n'importe quoi), noImplicitAny, strictFunctionTypes, etc. C'est la fondation de la sécurité TS. Sans ça, TS perd 70% de sa valeur. Sur tous les projets Krealabs : `strict: true` non négociable. Pour les vieux projets, migration progressive : activer strictNullChecks d'abord, corriger les erreurs, puis monter le reste un par un. Voir notre article sur TypeScript 5 strict mode.
Maîtriser ces 10 patterns transforme votre code TypeScript : moins de bugs en prod, refactoring plus serein, autocomplétion plus utile. Sur un projet de 30-50k lignes, le gain de productivité (et la baisse du nombre de bugs) est mesurable. Sur tous nos projets Krealabs Next.js et React Native, on utilise ces patterns quotidiennement. Pour discuter de TypeScript ou cadrer un projet avec une stack moderne, contactez-nous. Voir aussi notre lexique TypeScript, notre stack TypeScript, et nos services développement web.
Écrit par
Maxime Dubois
Fondateur · Krealabs
Découvrir l'équipe



