Introduction : pourquoi sans les generics TypeScript perd-il la moitié de sa puissance ?
Imaginez que vous êtes un architecte qui conçoit un bâtiment à partir de zéro à chaque fois, même s'il s'agit simplement de construire un autre garage identique. C'est à peu près ce que ressent un développeur qui écrit la même fonction pour les nombres, les chaînes et les tableaux, sans utiliser TypeScript generics. Cela vous semble familier ? Si vous maîtrisez déjà les types de base en TypeScript, mais que vous sentez que vous écrivez trop de code redondant, cet article est pour vous.
Les Generics sont un mécanisme qui permet de créer des composants (fonctions, classes, interfaces) qui fonctionnent avec différents types, plutôt qu'avec un seul type spécifique. Au lieu de copier du code, vous l'écrivez une fois, et le type est substitué automatiquement. Dans cet article, nous allons voir comment fonctionne la programmation générique en TypeScript, montrer des exemples de code concrets et donner des conseils pratiques qui rendront votre code plus propre et plus sûr.
Nous sommes PythonLib, et aujourd'hui nous parlons de TypeScript, car sans lui, le développement frontend moderne est impensable. C'est parti !
Que sont les generics en TypeScript et pourquoi en a-t-on besoin ?
Commençons par un exemple simple. Supposons que vous souhaitiez écrire une fonction qui renvoie l'argument qui lui est passé (fonction identité).
// Sans generics — on doit utiliser anyfunction identity(arg: any): any { return arg;}
// Ou écrire des surcharges pour chaque typefunction identityNumber(arg: number): number { return arg; }function identityString(arg: string): string { return arg; }La première option tue le typage — vous perdez l'information sur le type. La seconde vous oblige à multiplier des fonctions identiques. Les generics TypeScript résolvent ce problème avec élégance :
function identity<T>(arg: T): T { return arg;}
const num = identity<number>(42); // type numberconst str = identity('hello'); // type string (inférence de type)const bool = identity(true); // type booleanLe symbole <T> est un paramètre de type. Vous pouvez le nommer comme vous voulez (on utilise souvent T, U, V), mais il est préférable de donner des noms explicites s'il y en a plusieurs. Lorsque vous appelez la fonction, TypeScript substitue le type réel à la place de T. Si le type n'est pas spécifié explicitement, le compilateur l'infère automatiquement à partir de l'argument.
Ainsi, les types génériques (Generics) permettent de conserver un typage strict là où on devait auparavant utiliser any. Cela réduit le nombre de bugs et rend le code auto-documenté.
Syntaxe des generics : du simple au complexe
Fonctions génériques
Nous avons déjà vu un exemple de base. Complexifions : une fonction qui prend un tableau et renvoie son premier élément.
function getFirst<T>(arr: T[]): T | undefined { return arr[0];}
const firstNumber = getFirst([1, 2, 3]); // number | undefinedconst firstString = getFirst(['a', 'b', 'c']); // string | undefinedNotez que T[] est un raccourci pour Array<T>. Vous pouvez utiliser l'une ou l'autre forme.
Interfaces génériques
Les interfaces avec des generics sont un outil puissant pour décrire des structures de données. Par exemple, une réponse d'API typée :
interface ApiResponse<T> { data: T; status: number; message: string;}
// Utilisationconst userResponse: ApiResponse<{ id: number; name: string }> = { data: { id: 1, name: 'Alice' }, status: 200, message: 'OK'};Maintenant, une seule interface peut décrire les réponses pour les utilisateurs, les produits, les commandes — il suffit de substituer le type approprié.
Classes génériques
Les classes peuvent aussi être génériques. Un exemple classique est la file d'attente (Queue) :
c