Génériques
Écrire du code réutilisable et typer avec les génériques : syntaxe <T>, contraintes extends, cas concrets (Result, ApiResponse).
Les types qu'on a vus sont fixes. Les génériques permettent de paramétrer un type : le code s'adapte au type qu'on lui passe, tout en gardant la sécurité.
🔧 Pourquoi les génériques ?
Sans générique, on devrait écrire une version par type :
// ❌ Sans générique : duplication
function premierString(vals: string[]): string {
return vals[0];
}
function premierNumber(vals: number[]): number {
return vals[0];
}
function premierBoolean(vals: boolean[]): boolean {
return vals[0];
}Avec un générique, une seule fonction couvre tous les cas :
// ✅ Avec générique : un code, tous les types
function premier<T>(vals: T[]): T {
return vals[0];
}
const a = premier(["a", "b"]); // type : string
const b = premier([1, 2, 3]); // type : number
const c = premier([true, false]); // type : boolean📐 Syntaxe de base
Un générique est un type paramétré par une variable (souvent T). À l'appel, T est remplacé par le type réel. C'est l'équivalent d'une fonction, mais pour les types.
Un paramètre de type est la variable T dans <T>. Elle est déduite à l'appel à partir de l'argument. On peut en déclarer plusieurs : <T, U, V>.
Sur une fonction :
function identity<T>(valeur: T): T {
return valeur;
}
const s = identity("hello"); // string
const n = identity(42); // numberSur une interface ou un type :
interface Boite<T> {
contenu: T;
}
const boiteString: Boite<string> = { contenu: "hello" };
const boiteNumber: Boite<number> = { contenu: 42 };🔒 Contraintes avec extends
La contrainte (extends) restreint les types acceptés par un générique. T extends { nom: string } signifie « T doit avoir au moins un champ nom de type string ».
Sans contrainte, on ne peut rien supposer sur T :
function <>(: ): void {
.(.nom); // ❌ erreur : on ne connaît pas obj}Avec contrainte, on accède aux propriétés garanties :
function afficherNom<T extends { nom: string }>(obj: T): void {
console.log(obj.nom); // ✅ OK : T a au moins nom: string
}
afficherNom({ nom: "Alice", age: 25 });
afficherNom({ nom: "Bob" });🎯 Cas concrets
Le motif Result (générique paramétré)
Le type Result<T> qu'on a vu dans « Interfaces, unions et narrowing » est un générique :
type Result<T, E = string> = { ok: true; valeur: T } | { ok: false; erreur: E };
function diviser(a: number, b: number): Result<number> {
if (b === 0) return { ok: false, erreur: "Division par zéro" };
return { ok: true, valeur: a / b };
}
const r = diviser(10, 2);
if (r.ok) {
console.log(r.valeur); // number
}Le type ApiResponse
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User {
id: number;
nom: string;
}
interface Post {
id: number;
titre: string;
}
async function charger<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
return res.json() as Promise<ApiResponse<T>>;
}
// Usage : le type est connu à l'appel
const repUser = await charger<User>("/api/users/1");
console.log(repUser.data.nom);
const repPost = await charger<Post>("/api/posts/1");
console.log(repPost.data.titre);Fonction avec plusieurs génériques
function paire<T, U>(premier: T, second: U): [T, U] {
return [premier, second];
}
const p1 = paire("Alice", 25); // [string, number]
const p2 = paire(true, "ok"); // [boolean, string]📋 Points clés
- Générique (
<T>) : paramètre de type qui s'adapte à l'appel. - Contrainte (
extends) : restreint les types acceptés, permet d'accéder aux propriétés. - Déduction :
Test souvent déduit automatiquement, pas besoin de l'écrire. - Usage :
Result<T>,ApiResponse<T>,Boite<T>, fonctions réutilisables.
✅ Vérification des acquis
Quiz du chapitre
TypeScript
Terminez le quiz du chapitre pour le marquer comme complété.
Interfaces, unions et narrowing
Décrire des objets avec interface et type, combiner des possibles avec les unions (|), et restreindre le type avec le narrowing.
Utility types et type guards
Transformer des types existants avec Partial/Pick/Omit/Record, valider des données inconnues avec les type guards (v is T), et découvrir satisfies.