> Comprendre les generics en TypeScript : syntaxe <T>, fonctions, classes et interfaces génériques, contraintes extends, valeurs par défaut et types utilitaires. Avec des exemples concrets.

*Source : https://coder-studio.com/tutoriels/generics-typescript/*

---

[// TypeScript](https://coder-studio.com/tutoriels/)

# Les generics en TypeScript : le guide

Par **Kévin Papot** · publié le 16 février 2026

Les **generics** (ou types génériques) sont l’un des mécanismes les plus puissants de TypeScript, et aussi l’un des plus intimidants au premier abord. Pourtant, l’idée derrière les `<T>` est simple : écrire du code qui fonctionne avec **n’importe quel type**, tout en conservant un typage précis. Ce guide décortique les generics pas à pas, de la fonction basique aux types utilitaires de la bibliothèque standard.

## Le problème que résolvent les generics

Imaginez une fonction qui renvoie le premier élément d’un tableau. Sans generics, vous avez deux mauvaises options.

```
// Option 1 : figer le type, donc dupliquer la fonction pour chaque cas
function premierNombre(liste: number[]): number {
  return liste[0];
}

// Option 2 : utiliser any, et perdre toute sécurité de typage
function premier(liste: any[]): any {
  return liste[0]; // le retour est any : plus aucune autocomplétion
}
```

Avec `any`, vous perdez tout ce qui fait l’intérêt de TypeScript. Avec un type figé, vous devez réécrire la fonction pour chaque type de données. Les generics résolvent exactement ce dilemme : ils laissent l’appelant décider du type, sans le figer dans la définition.

Si les fondamentaux du langage ne sont pas encore solides, notre [guide TypeScript](https://coder-studio.com/tutoriels/typescript-guide/) pose les bases avant d’aborder les génériques.

## La syntaxe `<T>`

Un type générique se déclare entre chevrons, juste après le nom de la fonction. La convention veut qu’on l’appelle `T` (pour _Type_), mais ce n’est qu’un nom : `T`, `Elem` ou `Item` fonctionnent aussi bien.

```
function premier<T>(liste: T[]): T | undefined {
  return liste[0];
}

const n = premier([1, 2, 3]);       // T inféré : number → n est number | undefined
const s = premier(["a", "b", "c"]); // T inféré : string → s est string | undefined
const b = premier<boolean>([true]); // T explicite : boolean
```

Le point clé : TypeScript **infère** le type tout seul dans la plupart des cas. Vous écrivez `premier([1, 2, 3])`, et il comprend que `T` vaut `number`. Vous n’avez besoin de préciser `<boolean>` que lorsque l’inférence est impossible ou ambiguë. Le résultat garde son type exact, avec l’autocomplétion complète derrière.

## Plusieurs paramètres de type

Rien n’oblige à se limiter à un seul type variable. Une fonction qui combine deux valeurs peut en déclarer autant que nécessaire.

```
function paire<K, V>(cle: K, valeur: V): [K, V] {
  return [cle, valeur];
}

const p = paire("age", 30); // [string, number]

// Exemple concret : transformer un tableau en objet indexé
function indexer<T>(liste: T[], cle: (item: T) => string): Record<string, T> {
  const resultat: Record<string, T> = {};
  for (const item of liste) {
    resultat[cle(item)] = item;
  }
  return resultat;
}

const users = [{ id: "a", nom: "Léa" }, { id: "b", nom: "Tom" }];
const parId = indexer(users, (u) => u.id);
// parId.a est correctement typé { id: string; nom: string }
```

## Les contraintes avec `extends`

Un générique « nu » comme `T` peut être absolument n’importe quoi, ce qui limite ce que vous pouvez en faire : impossible d’accéder à `.length` ou à une propriété précise, puisque TypeScript ne sait rien de `T`. Le mot-clé `extends` impose une **contrainte** : `T` doit au minimum respecter une certaine forme.

```
// T doit posséder une propriété length de type number
function plusLong<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

plusLong("bonjour", "salut");     // ✅ les chaînes ont une length
plusLong([1, 2, 3], [4, 5]);      // ✅ les tableaux aussi
plusLong(10, 20);                 // ❌ number n'a pas de length
```

Un cas très courant : contraindre une clé à appartenir aux propriétés d’un objet, grâce à `keyof`. C’est le patron exact derrière une fonction de type « lodash `get` », entièrement typée.

```
function getProp<T, K extends keyof T>(objet: T, cle: K): T[K] {
  return objet[cle];
}

const article = { titre: "Guide", vues: 1200, publie: true };
const t = getProp(article, "titre");  // t est string
const v = getProp(article, "vues");   // v est number
getProp(article, "auteur");           // ❌ "auteur" n'est pas une clé valide
```

Ici, `K extends keyof T` garantit que la clé demandée existe vraiment, et `T[K]` renvoie le type exact de la propriété. Aucune erreur de nom de clé ne peut passer.

## Valeurs par défaut

Comme pour les paramètres classiques, un type générique peut recevoir une valeur par défaut avec `=`. Utile pour proposer un comportement courant sans forcer l’appelant à tout préciser.

```
interface Reponse<T = unknown> {
  succes: boolean;
  donnees: T;
}

// Sans préciser T, donnees est de type unknown
const r1: Reponse = { succes: true, donnees: "peu importe" };

// Avec un type explicite, donnees est précisément typé
const r2: Reponse<string[]> = { succes: true, donnees: ["a", "b"] };
```

## Classes et interfaces génériques

Les generics ne se limitent pas aux fonctions. Une **classe générique** est idéale pour modéliser une structure de données réutilisable, comme une pile (LIFO).

```
class Pile<T> {
  private elements: T[] = [];

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

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

  get taille(): number {
    return this.elements.length;
  }
}

const nombres = new Pile<number>();
nombres.empiler(1);
nombres.empiler(2);
const dernier = nombres.depiler(); // typé number | undefined
```

Une **interface générique** décrit de la même façon un contrat paramétré. Elle est omniprésente pour typer les fonctions de rappel ou les collections.

```
interface Depot<T> {
  parId(id: number): T | undefined;
  tous(): T[];
  ajouter(item: T): void;
}

interface Produit {
  id: number;
  nom: string;
}

// On implémente le dépôt spécialisé pour Produit
class DepotProduits implements Depot<Produit> {
  private data: Produit[] = [];
  parId(id: number) { return this.data.find((p) => p.id === id); }
  tous() { return this.data; }
  ajouter(p: Produit) { this.data.push(p); }
}
```

## Les types utilitaires reposent sur les generics

TypeScript fournit toute une boîte à outils de **types utilitaires**, tous construits sur des generics. Les connaître évite de réinventer la roue.

```
interface Utilisateur {
  id: number;
  nom: string;
  email: string;
}

// Partial<T> : toutes les propriétés deviennent optionnelles
function maj(id: number, champs: Partial<Utilisateur>) { /* ... */ }
maj(1, { nom: "Léa" }); // pas besoin de fournir id ni email

// Pick<T, K> : ne garder que certaines propriétés
type Apercu = Pick<Utilisateur, "id" | "nom">;

// Omit<T, K> : retirer certaines propriétés
type SansId = Omit<Utilisateur, "id">;

// Record<K, V> : construire un objet à clés/valeurs typées
const roles: Record<string, boolean> = { admin: true, invite: false };

// Readonly<T> : rendre l'objet non modifiable
const fige: Readonly<Utilisateur> = { id: 1, nom: "Tom", email: "t@ex.fr" };
```

`Partial`, `Pick`, `Omit`, `Record`, `Readonly`, `Required`… tous ces utilitaires ne sont que des generics prédéfinis. En les combinant, on transforme un type existant sans jamais le dupliquer, ce qui garde le code source unique et cohérent.

## Bonnes pratiques

Quelques repères pour utiliser les generics sans les rendre illisibles :

-   **N’introduisez un générique que s’il est réellement réutilisé.** Si un type variable n’apparaît qu’à un seul endroit, il ne sert probablement à rien.
-   **Contraignez avec `extends` dès que possible.** Un `T` totalement libre limite ce que vous pouvez en faire ; une contrainte débloque l’autocomplétion.
-   **Laissez l’inférence travailler.** Précisez `<T>` explicitement uniquement quand TypeScript ne peut pas deviner.
-   **Nommez clairement.** `T` convient pour un cas générique, mais `TCle` / `TValeur` clarifient les signatures complexes.

## En résumé

Les generics permettent d’écrire du code à la fois **réutilisable et rigoureusement typé** : une seule définition qui s’adapte à chaque type de données, sans jamais retomber dans `any`. Ils sont la colonne vertébrale des bibliothèques modernes et des types utilitaires de TypeScript. Commencez par des fonctions simples, ajoutez des contraintes `extends` quand vous en avez besoin, et vous manipulerez bientôt les `<T>` sans y penser.

## À lire ensuite

-   [type vs interface en TypeScript : quelle différence ?](https://coder-studio.com/tutoriels/type-vs-interface-typescript/)
-   [TypeScript : pourquoi et comment l’adopter](https://coder-studio.com/tutoriels/typescript-guide/)
-   [Les méthodes des tableaux en JavaScript](https://coder-studio.com/tutoriels/tableaux-javascript-methodes/)
