Introduction : que sont les Generics et pourquoi sont-ils importants
TypeScript est un langage fortement typé qui ajoute un système de types puissant à JavaScript. Cependant, le typage strict empêche parfois d'écrire un code flexible et réutilisable. C'est là que les Generics (types génériques) viennent à la rescousse.
Les Generics sont un outil qui permet de créer des composants (fonctions, classes, interfaces) fonctionnant avec différents types de données, sans perdre le contrôle des types. Imaginez que vous écrivez une fonction qui doit fonctionner à la fois avec des nombres, des chaînes de caractères et des tableaux. Sans les generics, vous devriez soit utiliser any (ce qui annule tous les avantages du typage), soit créer des fonctions séparées pour chaque type de données. Les Generics résolvent ce problème de manière élégante et sécurisée.
Dans cet article, nous allons voir pourquoi les generics sont nécessaires en TypeScript et, surtout, comment les utiliser en pratique — des fonctions simples aux classes complexes et aux contraintes de types.
Partie principale
1. Generics dans les fonctions : de la théorie à la pratique
L'exemple le plus simple et le plus courant d'utilisation des generics concerne les fonctions qui acceptent et renvoient des données du même type. Considérons un problème classique : une fonction qui retourne le premier élément d'un tableau.
Mauvaise approche (sans Generics) :
// Exemple avec any — on perd l'information sur le typefunction getFirst(arr: any[]): any { return arr[0];}
const num = getFirst([1, 2, 3]); // le type de num est any, on ne sait pas que c'est un nombreBonne approche (avec Generics) :
// Utilisation du type générique Tfunction getFirst<T>(arr: T[]): T { return arr[0];}
const num = getFirst([1, 2, 3]); // le type de num est numberconst str = getFirst(['a', 'b', 'c']); // le type de str est stringconst bool = getFirst([true, false]); // le type de bool est booleanComment cela fonctionne-t-il ? Nous déclarons un paramètre de type <T> (le nom peut être quelconque, mais par convention on utilise T, U, V). TypeScript déduit automatiquement le type concret en fonction de l'argument passé. C'est ce qu'on appelle l'inférence de types.
Vous pouvez également spécifier explicitement le type lors de l'appel de la fonction :
const num = getFirst<number>([1, 2, 3]); // spécification explicite du typeUn autre exemple utile est une fonction qui retourne un élément aléatoire d'un tableau :
function getRandom<T>(items: T[]): T { const randomIndex = Math.floor(Math.random() * items.length); return items[randomIndex];}
const randomNumber = getRandom([10, 20, 30]); // type — numberconst randomName = getRandom(['Anna', 'Ivan', 'Maria']); // type — string2. Generics dans les interfaces et les types
Les generics fonctionnent également très bien avec les interfaces. Cela permet de créer des structures de données flexibles capables de stocker des valeurs de différents types.
Imaginez que nous voulions décrire une réponse API qui peut contenir des données de n'importe quel type :
interface ApiResponse<T> { status: number; message: string; data: T;}
// Utilisation pour différents types de donnéesconst userResponse: ApiResponse<{ id: number; name: string }> = { status: 200, message: 'Succès', data: { id: 1, name: 'Alexei' }};
const listResponse: ApiResponse<string[]> = { status: 200, message: 'Liste reçue', data: ['pomme', 'banane', 'orange']};On peut également utiliser les generics avec les alias de types (type) :
type Result<T> = { success: boolean; value: T; error?: string;};
const successResult: Result<number> = { success: true, value: 42 };const errorResult: Result<string> = { success: false, error: 'Erreur de chargement', value: '' };Notez que nous pouvons co