Écrire du code réutilisable SANS perdre le typage
Utilisez les flèches, cliquez ou glissez pour naviguer
1. Comprendre le problème que les generics résolvent
Duplication de code quand on veut la même logique pour plusieurs types
2. Écrire une fonction générique identity<T>
Un seul code, tous les types
3. Utiliser les generics sur les interfaces
Box<T>, ApiResponse<T>
4. Savoir quand utiliser un generic vs un type concret
Le bon outil au bon moment
Montrer le problème
3 fonctions identiques pour 3 types différents — c'est de la duplication
any : la fausse solution
On perd le typage — puis introduire <T> comme vraie solution
Live coding : fonctions génériques
identity<T>, first<T>, makePair<T> — la magie de l'inférence
Generics sur les interfaces
Box<T>, ApiResponse<T> — un seul type, réutilisable partout
Exercice live : map<T, U>
Deux paramètres de type — transformer un type en un autre
Même logique, 3 types différents → 3 fonctions copiées-collées
function identityNumber(value: number): number {
return value;
}
function identityString(value: string): string {
return value;
}
function identityBoolean(value: boolean): boolean {
return value;
}
// 3 fonctions qui font EXACTEMENT la même chose! ❌
⚠️ Et si on veut supporter un 4e type? On copie-colle encore?
1️⃣
Duplication
3 fois le même code
2️⃣
Maintenance
Un bug = 3 endroits à corriger
3️⃣
Incomplet
Nouveau type = nouvelle copie
// Imaginons : on veut ajouter un log dans chaque fonction
function identityNumber(value: number): number {
console.log("received:", value); // ← à ajouter dans les 3 fonctions!
return value;
}
🎯 Copier-coller = dette technique. On veut UNE seule fonction.
Une seule fonction qui marche pour TOUT type
✅ Une seule implémentation
✅ Le type est préservé — pas de perte d'information
✅ Réutilisable — même pour les types qu'on ne connaît pas encore
// Le rêve :
function identity<T>(value: T): T {
return value;
}
anyUne seule fonction avec any — ça compile, mais...
function identity(value: any): any {
return value;
}
✅ Une seule fonction
Plus de duplication
❌ On perd le typage
TypeScript ne vérifie plus rien
Avec any, TypeScript laisse passer N'IMPORTE QUOI
function identity(value: any): any {
return value;
}
const result = identity(5);
// TypeScript laisse passer ça :
result.toUpperCase(); // ❌ Compile! Mais crash à l'exécution!
result.map(x => x); // ❌ Compile! Mais 5 n'est pas un tableau!
🎯 any = désactiver TypeScript. C'est comme enlever les freins d'une voiture.
Un paramètre de type — comme un paramètre de fonction, mais pour les types
function identity<T>(value: T): T {
return value;
}
<T>
Paramètre de type
Un placeholder
value: T
Paramètre typé
T sera remplacé
: T
Retour typé
Même type que l'entrée
❌ any
function identity(value: any): any {
return value;
}
• TypeScript ne vérifie rien
• result.toUpperCase() compile
• Erreurs découvertes à l'exécution
✅ <T>
function identity<T>(value: T): T {
return value;
}
• TypeScript préserve le type
• result.toUpperCase() → erreur!
• Erreurs attrapées AVANT l'exécution
const result = identity(5); // T est inféré comme number
result.toUpperCase(); // ❌ Erreur TS! number n'a pas toUpperCase
🎯 <T> préserve l'info de type — any la détruit
1️⃣ Déclarer la fonction avec un paramètre de type
function identity<T>(value: T): T {
return value;
}
2️⃣ Appeler en spécifiant le type explicitement
identity<string>("hello"); // T = string
identity<number>(42); // T = number
3️⃣ Laisser TypeScript inférer T automatiquement
identity("hello"); // T = string — automatique!
identity(42); // T = number — automatique!
TypeScript devine T tout seul — pas besoin de le spécifier!
Appel explicite
identity<string>("hi");
// Verbeux mais clair
Appel implicite (inférence)
identity("hi");
// TypeScript comprend que T = string
const a = identity("hello"); // a: string
const b = identity(42); // b: number
const c = identity(true); // c: boolean
// Chaque appel a le bon type — sans rien spécifier!
💡 Règle pratique : ne spécifiez <T> que si TypeScript ne peut pas inférer tout seul
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
<T>
Le type des éléments
T[]
Un tableau de T
T | undefined
Le premier ou rien
first([1, 2, 3]); // number | undefined → 1
first(["a", "b"]); // string | undefined → "a"
first([]); // undefined
function makePair<T>(a: T, b: T): [T, T] {
return [a, b];
}
Les deux paramètres doivent être du même type T
makePair(1, 2); // [number, number]
makePair("a", "b"); // [string, string]
// makePair(1, "b"); // ❌ Erreur! number ≠ string
💡 T est le même partout dans la fonction — c'est ça la garantie!
T est une convention, pas une obligation — mais tout le monde l'utilise
T
Type
Le type principal, le plus courant
identity<T>(value: T): T
K
Key
Pour les clés d'un objet
getProp<K>(key: K)
V
Value
Pour les valeurs associées
Map<K, V>
E
Element
Pour les éléments d'une collection
Array<E>
// Noms explicites aussi possibles :
function merge<TInput, TOutput>(...) { ... }
Une boîte qui contient n'importe quel type
interface Box<T> {
value: T;
}
T = le type de ce qui est DANS la boîte
Box<string> → une boîte de string
Box<number> → une boîte de number
interface Box<T> {
value: T;
}
const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };
✅ Type correct
stringBox.value.toUpperCase();
❌ Erreur TypeScript
numberBox.value.toUpperCase();
// number n'a pas toUpperCase!
❌ Erreur : type mismatch
const badBox: Box<string> = { value: 42 };
// Type 'number' is not assignable to 'string'
Un cas réel : chaque API retourne la même structure, mais des données différentes
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Réponse avec un utilisateur
type UserResponse = ApiResponse<User>;
// Réponse avec une liste de produits
type ProductsResponse = ApiResponse<Product[]>;
💡 Un seul type ApiResponse, réutilisable pour TOUTE les réponses API!
interface User { name: string; age: number; }
interface Product { title: string; price: number; }
ApiResponse<User>
{
data: { name: "Alice", age: 25 },
status: 200,
message: "OK"
}
data.name ✅ auto-complété!
ApiResponse<Product[]>
{
data: [{ title: "Book", price: 10 }],
status: 200,
message: "OK"
}
data[0].price ✅ auto-complété!
🎯 Un seul type, réutilisable partout — data a toujours le bon type!
✅ Utiliser un generic quand
• La même logique s'applique à plusieurs types
• Tu veux préserver l'info de type
• Le type est déterminé par l'appelant
function first<T>(arr: T[]): T
// Marche pour string[], number[], ...
✅ Utiliser un type concret quand
• Tu connais le type à l'avance
• La fonction ne marche que pour un type
• Pas besoin de flexibilité
function double(n: number): number
// Ne fait sens que pour number
🎯 Pas de generic "au cas où" — un generic doit servir à AU MOINS 2 types
<T, U> — l'entrée et la sortie peuvent être différents
T
Type d'entrée
U
Type de sortie
// T = type des éléments en entrée
// U = type des éléments en sortie
function map<T, U>(...): U[]
1️⃣ La signature
function map<T, U>(arr: T[], fn: (item: T) => U): U[]
2️⃣ L'implémentation
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
const result: U[] = [];
for (const item of arr) {
result.push(fn(item));
}
return result;
}
3️⃣ L'appel
map([1, 2, 3], n => n.toString());
// T = number, U = string → string[]
// number → string
map([1, 2, 3], n => n.toString());
// → ["1", "2", "3"] (T=number, U=string)
// string → number
map(["1", "2"], s => parseInt(s));
// → [1, 2] (T=string, U=number)
// User → string
map(users, u => u.name);
// → ["Alice", "Bob"] (T=User, U=string)
💡 TypeScript infère T et U automatiquement — pas besoin de spécifier map<number, string>(...)
Piège 1 : Confondre <T> avec un type concret
T est un type qui existe
❌ Non! T est un placeholder
T est remplacé à l'appel
✅ Comme un paramètre de fonction
Piège 2 : La syntaxe <T> intimide
Rassurer : <T> c'est juste un paramètre, comme (x: number) mais pour les types
// Paramètre de fonction : la valeur change à chaque appel
function f(x: number) { ... }
// Paramètre de type : le TYPE change à chaque appel
function g<T>(x: T) { ... }
Piège 3 : Aller trop vite sur <T, U>
Commencer par un seul T. <T, U> vient naturellement quand on a besoin de transformer un type en un autre.
<T> = placeholder
Un paramètre de type — comme un paramètre de fonction, mais pour les types
Inférence automatique
TypeScript devine T — pas besoin de le spécifier à chaque appel
Réutilisable SANS perdre le typage
Une seule implémentation, tous les types, zéro any
Conventions
T (Type), K (Key), V (Value), E (Element)
Un generic = un paramètre pour les types
Comme (x: number) prend une valeur, <T> prend un type
Les generics = écrire une fois, utiliser pour tous les types
function identity<T>(value: T): T { return value; }