TypeScript avancé : types conditionnels, templates littéraux et inference
TypeScript est bien plus qu’un simple système de types basique. Une fois qu’on maîtrise les types conditionnels, les templates littéraux, et l’inférence, on peut construire des types qui s’adaptent dynamiquement à vos données.
Je vais vous montrer des cas concrets issus de mon code pour illustrer ces concepts.
Types conditionnels
Un type conditionnel agit comme un if au niveau du type :
type IsString<T> = T extends string ? true: false; |
Cas concret : extraire les promesses
Dans mon agent autonome, j’avais besoin d’unifier des retours synchrones et asynchrones :
type AwaitResult<T> = T extends Promise<infer U> ? U : T; |
Union distribution
Quand on passe une union à un type conditionnel, il distribue automatiquement :
type ToArray<T> = T extends unknown ? T[] : never; |
Pour éviter la distribution, on encapsule :
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never; |
Inférence avec infer
Le mot-clé infer permet de capturer un type dans une condition pour le réutiliser.
Extraire le type d’un tableau
type ArrayItem<T> = T extends Array<infer U> ? U : never; |
Cas concret : typage d’une API
J’ai utilisé infer pour typer automatiquement les retours d’une API Express :
type ApiResponse<T> = { |
Inférer les paramètres d’une fonction
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never; |
Templates littéraux
Les template literal types permettent de construire des types à partir de chaînes dynamiques.
Routes typées
C’est la fonctionnalité que j’utilise le plus au quotidien :
type Route = `/api/${string}`; |
Cas concret : API REST typée
J’ai typé toutes les routes de mon API REST avec des templates littéraux :
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE"; |
Manipulation de chaînes de types
TypeScript fournit des utilitaires pour transformer les chaînes :
type EventName = "user:created" | "user:updated" | "post:deleted"; |
Combiner le tout : un vrai cas d’usage
Voici comment j’ai combiné ces trois concepts pour typer mon système d’événements :
// Définition des événements |
Validation au runtime via les templates
On peut même faire de la validation d’inputs avec les template types :
type HexColor = `#${string}`; |
Prédicats de type avancés
Avec is, on crée des guards qui affinent le type :
function isStringArray(value: unknown): value is string[] { |
Bonus : mapped types avec template keys
On peut créer des types à partir de clés transformées :
type Getters<T> = { |
type Setters<T> = { |
Conclusion
Les types conditionnels, l’inférence avec infer, et les templates littéraux sont les piliers du typage avancé en TypeScript. Ils permettent de construire des types dynamiques, auto-adaptatifs, qui suivent la logique de votre métier.
J’utilise ces patterns quotidiennement pour typer mes APIs, mes événements, et mes systèmes de routing. Le compilateur détecte les incohérences à la compilation plutôt qu’à l’exécution. C’est un gain de temps considérable.
Et vous, quel pattern TypeScript vous semble le plus utile au quotidien ?