Sans génériques, on a souvent le choix entre dupliquer du code pour chaque type, ou tout typer en any et perdre les vérifications du compilateur. Les génériques résolvent ce dilemme : ils décrivent une relation entre les types d'entrée et de sortie, sans figer le type exact.
Le principe, en une fonction
function identity<T>(value: T): T {
return value;
}
const nombre = identity(42); // T déduit comme number
const texte = identity("bonjour"); // T déduit comme stringT est un paramètre de type : TypeScript le déduit automatiquement depuis l'argument passé, et garantit que le type de retour correspond exactement au type d'entrée. Pas de any, pas de duplication.
Un cas concret : une fonction fetch typée
async function fetchJSON<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json() as Promise<T>;
}
type User = { id: number; name: string };
const user = await fetchJSON<User>("/api/user/1");
// user est typé comme User : user.name est vérifié à la compilationContraindre un générique avec extends
On peut restreindre les types acceptés avec extends, pour garder de la souplesse tout en imposant une forme minimale.
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
getLength("hello"); // ok, string a .length
getLength([1, 2, 3]); // ok, array a .length
getLength(42); // erreur : number n'a pas .lengthC'est exactement ce mécanisme qui alimente les types utilitaires natifs de TypeScript comme Partial<T>, Pick<T, K> ou Record<K, V> : des génériques bien conçus, réutilisés partout dans l'écosystème.
