Métaprogrammation en TypeScript : comment écrire du code qui écrit du code

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : la magie cachée dans les types

\n\n

Imaginez que vous n'écrivez pas simplement du code, mais un ensemble d'instructions pour le compilateur lui-même, qui, en fonction de vos règles, génère des constructions sûres et auto-documentées. C'est cela, la métaprogrammation en TypeScript — la capacité d'écrire du code qui fonctionne au niveau des types, analyse et transforme d'autres types lors de la compilation.

\n\n

De nombreux développeurs s'arrêtent aux génériques et interfaces de base, sans même soupçonner l'arsenal puissant qui se cache derrière les mots-clés infer, extends et les constructions conditionnelles. Dans cet article, nous allons explorer les techniques avancées de métaprogrammation TypeScript qui vous feront passer d'un simple utilisateur du langage à un véritable architecte.

\n\n

1. Types conditionnels : le if au niveau des types

\n\n

Les types conditionnels sont la base de toute programmation au niveau des types. Ils fonctionnent comme un opérateur ternaire, mais au niveau du système de types :

\n\n
type IsString<T> = T extends string ? "yes" : "no";\n\ntype Test1 = IsString<"hello">;   // "yes"\ntype Test2 = IsString<42>;        // "no"\n
\n\n

Mais la véritable magie commence lorsque nous les combinons avec des génériques. Par exemple, créons un type qui extrait uniquement les clés de type chaîne d'un objet :

\n\n
type StringKeys<T> = {\n  [K in keyof T]: T[K] extends string ? K : never\n}[keyof T];\n\ninterface User {\n  name: string;\n  age: number;\n  email: string;\n}\n\ntype UserStringKeys = StringKeys<User>; // "name" | "email"\n
\n\n

Cet exemple montre comment les types avancés TypeScript permettent de filtrer et transformer des structures de données à l'étape de compilation. Vous programmez littéralement une logique qui s'exécute avant que le code n'atteigne l'exécution.

\n\n

2. Infer : extraire des types à partir d'autres types

\n\n

Le mot-clé infer est sans doute l'outil le plus puissant de l'arsenal de la métaprogrammation TypeScript. Il permet de "regarder à l'intérieur" d'un type et d'en extraire ses parties constitutives. Prenons un exemple classique : obtenir le type de retour d'une fonction :

\n\n
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;\n\ntype Fn = (x: number) => string;\ntype FnReturn = ReturnType<Fn>; // string\n
\n\n

Mais infer peut faire bien plus. Extrayons le type du premier argument d'une fonction :

\n\n
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;\n\ntype Handler = (id: number, name: string) => void;\ntype HandlerFirstArg = FirstArg<Handler>; // number\n
\n\n

Et que diriez-vous d'une analyse récursive des promesses ? Créons un type qui "déroule" toutes les promesses imbriquées :

\n\n
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;\n\ntype DeepPromise = Promise<Promise<Promise<number>>>;\ntype Unwrapped = UnwrapPromise<DeepPromise>; // number\n
\n\n

Remarquez la récursion — c'est l'une des techniques avancées TypeScript les plus élégantes, permettant de traiter des structures infiniment imbriquées.

\n\n

3. Mapped types : transformation d'objets

\n\n

Les Mapped types permettent de créer de nouveaux types à partir de types existants en parcourant leurs clés. C'est un outil indispensable pour créer des utilitaires génériques :

\n\n
type Readonly<T> = {\n  readonly [K in keyof T]: T[K];\n};\n\ntype Partial<T> = {\n  [K in keyof T]?: T[K];\n};\n
\n\n

Mais nous pouvons aller plus loin. Créons un type qui rend toutes les propriétés optionnelles, mais uniquement si ce sont des chaînes :

\n\n
type PartialStringProps<T> = {\n  [K in keyof T]: T[K] extends string ? T[K] | undefined : T[K];\n};\n\ninterface Config {\n  nam                

Blogs

Book Recommendations