Génériques TypeScript pour le code quotidien : guide pratique

Frontend2026-10-04TryQuickToolBox

Vous écrivez une fonction qui fonctionne avec n'importe quel type, mais TypeScript continue de se plaindre à propos de any. Ou vous avez copié la même fonction trois fois juste pour gérer différents types. Les génériques résolvent ce problème—ils vous permettent d'écrire du code qui fonctionne avec plusieurs types tout en préservant la sécurité de type. Mais beaucoup de développeurs les évitent ou les surutilisent. Ce guide vous montre comment utiliser les génériques dans le code quotidien sans prise de tête.

Que sont les génériques et pourquoi s'y intéresser ?

Les génériques sont un moyen de paramétrer les types. Au lieu de coder en dur un type comme string ou number, vous utilisez une variable de type (souvent T) qui est remplacée par un type concret lorsque la fonction ou la classe est utilisée.

Pourquoi est-ce important ? Parce que cela vous permet d'écrire du code réutilisable qui reste typé. Sans génériques, vous devriez soit dupliquer le code, soit utiliser any, ce qui va à l'encontre du but de TypeScript.

Fonctions génériques : la brique de base

Commençons par un exemple simple. Supposons que vous voulez une fonction qui retourne le premier élément d'un tableau.

function firstElement(arr: any[]): any {
  return arr[0];
}

Cela fonctionne, mais vous perdez l'information de type. Si vous passez un tableau de chaînes, le type de retour est any, pas string. Maintenant avec les génériques :

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

Maintenant TypeScript sait que si vous passez un string[], vous obtenez un string | undefined en retour. Le type est préservé.

Vous pouvez aussi utiliser plusieurs paramètres de type :

function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}

Interfaces et alias de type génériques

Les génériques ne se limitent pas aux fonctions. Vous pouvez créer des interfaces et des alias de type génériques pour décrire des structures de données flexibles.

interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

C'est particulièrement utile pour les réponses d'API. Au lieu de définir une nouvelle interface pour chaque point de terminaison, vous pouvez définir un ApiResponse<T> générique :

interface ApiResponse<T> {
  data: T;
  status: number;
}

type User = { id: number; name: string };

const response: ApiResponse<User> = {
  data: { id: 1, name: "Alice" },
  status: 200,
};

Contraintes génériques : ajouter des règles

Parfois, vous voulez restreindre les types qui peuvent être utilisés avec votre générique. C'est là que les contraintes entrent en jeu. Utilisez le mot-clé extends pour exiger que le type ait certaines propriétés.

function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}

getLength("hello"); // OK
getLength([1, 2, 3]); // OK
getLength(42); // Erreur : number n'a pas de length

Les contraintes rendent vos génériques plus prévisibles et évitent les erreurs d'exécution.

Classes génériques

Les classes peuvent aussi être génériques. C'est pratique pour des structures de données comme les piles, les files d'attente ou les dépôts.

class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
const top = numberStack.pop(); // number | undefined

Quand utiliser les génériques (et quand ne pas le faire)

Les génériques sont puissants, mais ils peuvent rendre le code plus difficile à lire s'ils sont surutilisés. Voici un guide rapide :

Utilisez les génériques quand Évitez les génériques quand
Vous devez préserver les relations de type Un simple type union ou any suffit
Vous écrivez des utilitaires réutilisables Le type est toujours le même
Vous voulez éviter la duplication de code Cela rend le code plus difficile à comprendre

Une bonne règle empirique : si vous vous surprenez à écrire la même logique pour plusieurs types, envisagez les génériques. Si vous ajoutez un troisième paramètre de type et que personne ne le comprend, faites marche arrière.

Modèles courants et bonnes pratiques

Exemple concret : un client API générique

Rassemblons le tout avec un exemple pratique : un client API simple qui récupère des données et renvoie une réponse typée.

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json() as Promise<T>;
}

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

async function main() {
  const post = await fetchData<Post>("https://jsonplaceholder.typicode.com/posts/1");
  console.log(post.title); // string
}

Ce modèle est courant dans le code frontend. Vous obtenez la sécurité de type sans vous répéter pour chaque point de terminaison.

FAQ

Que signifie la syntaxe <T> en TypeScript ?

<T> déclare un paramètre de type. C'est un espace réservé pour un type qui sera spécifié lorsque la fonction, l'interface ou la classe est utilisée. Vous pouvez le nommer comme vous voulez, mais T est conventionnel pour un type unique.

Puis-je utiliser plusieurs paramètres de type génériques ?

Oui. Vous pouvez en définir autant que nécessaire, séparés par des virgules : <T, U, V>. Utilisez des noms descriptifs pour garder votre code lisible.

Comment contraindre un générique à n'accepter que des objets ?

Utilisez T extends object. Cela garantit que T est un type objet et non une primitive comme string ou number. Vous pouvez aussi contraindre à des formes spécifiques, comme T extends { id: number }.

Les génériques sont un élément essentiel pour écrire du TypeScript maintenable. Si vous travaillez avec des données JSON et souhaitez les valider ou les formater rapidement, essayez notre JSON Formatter pour embellir et inspecter vos réponses d'API.