Les génériques TypeScript au quotidien
Vous écrivez une fonction qui renvoie le premier élément d'un tableau. Vous voulez qu'elle fonctionne pour les nombres, les chaînes et les objets, mais aussi que le type de retour corresponde au type d'entrée. Sans génériques, vous seriez obligé d'utiliser any et de perdre toute sécurité de typage. C'est là que les génériques TypeScript entrent en jeu.
Les génériques vous permettent d'écrire du code qui fonctionne avec plusieurs types tout en préservant les informations de type spécifiques. Dans ce guide, nous allons explorer des patterns pratiques pour utiliser les génériques dans le code TypeScript de tous les jours.
Que sont les génériques ?
Les génériques sont un moyen de paramétrer les types. Tout comme les fonctions prennent des paramètres, les génériques prennent des paramètres de type. Ils vous permettent de créer des composants réutilisables qui fonctionnent sur une variété de types plutôt qu'un seul.
Considérez cette fonction identité :
function identity<T>(arg: T): T {
return arg;
}
Ici, T est une variable de type. Quand vous appelez identity(42), TypeScript infère T comme number. Quand vous appelez identity("hello"), T devient string. Le type de retour correspond toujours au type d'entrée.
Les fonctions génériques en pratique
Regardons un scénario courant : une fonction qui enveloppe une valeur dans un objet.
function wrapInObject<T>(value: T): { value: T } {
return { value };
}
Sans génériques, vous devriez utiliser any ou unknown, et les appelants perdraient les informations de type. Avec les génériques, le type de retour est précisément { value: T }.
Un autre exemple quotidien est une fonction qui renvoie le dernier élément d'un tableau :
function last<T>(arr: T[]): T | undefined {
return arr[arr.length - 1];
}
TypeScript infère T à partir de l'argument tableau, donc last([1, 2, 3]) renvoie number | undefined.
Contraintes génériques
Parfois, vous voulez restreindre les types qui peuvent être utilisés avec votre générique. Par exemple, vous pourriez avoir besoin d'accéder à une propriété qui n'existe que sur certains types. Vous pouvez utiliser le mot-clé extends pour ajouter une contrainte.
function getLength<T extends { length: number }>(item: T): number {
return item.length;
}
Maintenant T doit avoir une propriété length de type number. Cela fonctionne pour les chaînes, les tableaux et tout objet avec une propriété length.
Vous pouvez aussi utiliser des contraintes avec des interfaces :
interface HasId {
id: string;
}
function findById<T extends HasId>(items: T[], id: string): T | undefined {
return items.find(item => item.id === id);
}
Cela garantit que les éléments passés à findById ont une propriété id.
Interfaces et alias de type génériques
Les génériques ne se limitent pas aux fonctions. Vous pouvez définir des interfaces et des alias de type génériques pour créer des structures de données flexibles.
interface ApiResponse<T> {
data: T;
status: number;
error?: string;
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
const data = await response.json();
return { data, status: response.status };
}
Maintenant vous pouvez typer précisément les réponses d'API :
interface User {
id: number;
name: string;
}
const userResponse = await fetchData<User>('/api/user');
// userResponse.data est de type User
Classes génériques
Les classes peuvent aussi être génériques. Un exemple courant est une pile simple :
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
const num = numberStack.pop(); // number | undefined
Génériques dans les composants React
Si vous construisez des composants React, les génériques peuvent rendre vos composants plus réutilisables. Par exemple, un composant de liste qui affiche des éléments de n'importe quel type :
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map((item, index) => <li key={index}>{renderItem(item)}</li>)}</ul>;
}
Maintenant vous pouvez utiliser List avec n'importe quel type et obtenir une vérification complète du callback renderItem.
Pièges courants et bonnes pratiques
- N'abusez pas des génériques. Si une fonction ne fonctionne qu'avec un type spécifique, utilisez simplement ce type. Les génériques ajoutent de la complexité.
- Utilisez des noms de paramètres de type descriptifs. Bien que
Tsoit courant, pour plusieurs paramètres utilisez des noms commeTInput,TOutput. - Préférez les contraintes à
any. Les contraintes vous donnent la sécurité de typage tout en restant flexibles. - Rappelez-vous que les génériques sont effacés à l'exécution. Ils n'existent qu'à la compilation.
Comparaison : génériques vs any vs types d'union
| Approche | Sécurité de typage | Flexibilité | Cas d'usage |
|---|---|---|---|
any |
Aucune | Élevée | Migration JS, prototypes rapides |
| Types d'union | Bonne | Limitée aux types connus | Quand vous connaissez tous les types possibles |
| Génériques | Forte | Élevée | Composants réutilisables avec types préservés |
FAQ
Quand dois-je utiliser des génériques au lieu de any ?
Utilisez des génériques chaque fois que vous voulez préserver les informations de type à travers une fonction ou un composant. any désactive la vérification de type, tandis que les génériques la conservent. Si vous êtes tenté d'utiliser any pour faire fonctionner quelque chose, demandez-vous si un générique ne pourrait pas résoudre le problème plus sûrement.
Puis-je utiliser plusieurs paramètres de type ?
Oui. Vous pouvez définir des fonctions avec plusieurs paramètres de type, comme function pair<K, V>(key: K, value: V): [K, V]. Utilisez des noms descriptifs quand il y en a plus d'un.
Comment les génériques fonctionnent-ils avec les fonctions fléchées dans les fichiers .tsx ?
Dans les fichiers .tsx, <T> peut être ambigu avec JSX. Utilisez une virgule finale : const identity = <T,>(arg: T): T => arg; ou utilisez extends unknown : <T extends unknown>.
Les génériques sont une fonctionnalité puissante qui peut rendre votre code TypeScript plus réutilisable et plus sûr. Commencez par des fonctions génériques simples, puis appliquez-les progressivement aux interfaces et aux classes. Si vous avez besoin de formater ou valider des données JSON avec sécurité de typage, découvrez notre outil JSON Formatter pour inspecter et embellir rapidement votre JSON.