Cours JavaScript et TypeScript · Module 6: Bases de TypeScript

Les fonctions generic

14 min de lecture
Dans cette leçon11

Bienvenue de nouveau dans le laboratoire de génétique le plus avancé de Jurassic Park ! Le Dr Henry Wu nous invite aujourd'hui à explorer l'un des mécanismes les plus puissants de TypeScript : les fonctions generic. Tout comme l'ingénierie génétique avancée nous permet de créer de nouvelles espèces de dinosaures avec un ADN différent, les fonctions generic permettent de créer des solutions flexibles et réutilisables qui fonctionnent avec différents types de données tout en conservant une sécurité de types complète.

Que sont les fonctions generic ?

Les fonctions generic sont des fonctions capables de fonctionner avec différents types de données tout en gardant un contrôle complet sur les types. Au lieu de définir un type précis pour un paramètre et une valeur de retour, on utilise des paramètres de type (généralement notés T, U, K, etc.), qui sont « remplis » avec des types concrets au moment où la fonction est appelée.

Imagine qu'au lieu de créer une fonction séparée pour chaque espèce de dinosaure, tu puisses créer une seule fonction generic qui fonctionnera pour toutes les espèces !

Syntaxe de base des fonctions generic

Voyons comment les fonctions generic se définissent et s'utilisent en TypeScript :

1// Simple generic function - returns the same element that was passed
2function geneticIdentity<T>(obj: T): T {
3  return obj;
4}
5
6// Use with different types
7const dnaChain: string = geneticIdentity<string>("ACGTACGT");
8const triceratopsAge: number = geneticIdentity<number>(12);
9const activeStatus: boolean = geneticIdentity<boolean>(true);
10
11// TypeScript can often infer the generic type from the passed argument
12const aggressionLevel = geneticIdentity(7); // TypeScript infers T as number

Dans l'exemple ci-dessus :

  1. On définit une fonction generic geneticIdentity avec le paramètre de type T
  2. La fonction prend un argument de type T et retourne une valeur du même type
  3. On utilise la fonction avec différents types : string, number, boolean
  4. Dans le dernier exemple, TypeScript infère automatiquement le type generic à partir de l'argument passé

Les fonctions generic à plusieurs paramètres de type

Une fonction generic peut avoir plusieurs paramètres de type, ce qui permet des opérations plus complexes :

1// Generic function with two type parameters
2function combineDinosaurData<T, U>(first: T, second: U): [T, U] {
3  return [first, second];
4}
5
6// Sample usage
7interface BasicDinosaurData {
8  name: string;
9  species: string;
10  age: number;
11}
12
13interface HealthData {
14  bodyTemperature: number;
15  heartRate: number;
16  energyLevel: number;
17}
18
19const rexBasic: BasicDinosaurData = {
20  name: "Rex",
21  species: "Tyrannosaurus",
22  age: 7
23};
24
25const rexHealth: HealthData = {
26  bodyTemperature: 38.2,
27  heartRate: 76,
28  energyLevel: 8.5
29};
30
31// Combining different types of data
32const fullRexData = combineDinosaurData<BasicDinosaurData, HealthData>(
33  rexBasic,
34  rexHealth
35);
36
37console.log(`${fullRexData[0].name} has body temperature ${fullRexData[1].bodyTemperature}°C`);

Les contraintes generic

Parfois, on souhaite que nos fonctions generic ne fonctionnent qu'avec des types de données précis, satisfaisant certaines exigences. On peut y parvenir grâce aux contraintes generic, via le mot-clé extends :

1// Defining an interface describing minimum requirements
2interface BasicDinosaurInfo {
3  id: string;
4  name: string;
5  species: string;
6}
7
8// Generic function with constraint - T must contain at least the properties from BasicDinosaurInfo
9function displayDinosaurInfo<T extends BasicDinosaurInfo>(dino: T): void {
10  console.log(`ID: ${dino.id}`);
11  console.log(`Name: ${dino.name}`);
12  console.log(`Species: ${dino.species}`);
13
14  // We can also access additional properties that may be present in T
15  console.log("Additional information:");
16  const basicKeys = Object.keys({} as BasicDinosaurInfo);
17  Object.entries(dino).forEach(([key, value]) => {
18    if (!basicKeys.includes(key)) {
19      console.log(`- ${key}: ${value}`);
20    }
21  });
22}
23
24// Example usage with different types satisfying the constraint
25interface TRex extends BasicDinosaurInfo {
26  biteForce: number;
27  runSpeed: number;
28}
29
30const rex: TRex = {
31  id: "TREX-001",
32  name: "Rexy",
33  species: "Tyrannosaurus Rex",
34  biteForce: 12800,
35  runSpeed: 32
36};
37
38interface Velociraptor extends BasicDinosaurInfo {
39  intelligence: number;
40  groupHunting: boolean;
41}
42
43const raptor: Velociraptor = {
44  id: "VELOC-001",
45  name: "Blue",
46  species: "Velociraptor",
47  intelligence: 9,
48  groupHunting: true
49};
50
51// Both calls will work because both types extend BasicDinosaurInfo
52displayDinosaurInfo(rex);
53displayDinosaurInfo(raptor);
54
55// This would not work because required properties are missing
56// displayDinosaurInfo({ weight: 5000, age: 12 });

Dans l'exemple ci-dessus :

  1. On définit l'interface BasicDinosaurInfo avec les exigences minimales
  2. On crée une fonction generic qui ne fonctionne qu'avec des types étendant cette interface
  3. La fonction peut utiliser en toute sécurité les propriétés exigées par l'interface
  4. On peut utiliser la fonction avec différents types de dinosaures, tant qu'ils respectent les exigences de base

Les contraintes avec keyof

Le mot-clé keyof permet de contraindre un paramètre generic aux clés d'un autre type. C'est extrêmement utile pour créer des fonctions qui opèrent sur les clés d'un objet :

1// Function retrieving a property value based on its key
2function getDinosaurProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
3  return obj[key];
4}
5
6// Sample dinosaur data
7interface DinosaurData {
8  id: string;
9  name: string;
10  species: string;
11  age: number;
12  weight: number;
13  predator: boolean;
14  traits: string[];
15}
16
17const triceratops: DinosaurData = {
18  id: "TRIC-001",
19  name: "Tricky",
20  species: "Triceratops",
21  age: 15,
22  weight: 9000,
23  predator: false,
24  traits: ["horns", "head shield", "strong legs"]
25};
26
27// Using the function with different keys
28const triceratopsName = getDinosaurProperty(triceratops, "name");
29console.log(`Name: ${triceratopsName}`); // TypeScript knows this is a string
30
31const triceratopsAge = getDinosaurProperty(triceratops, "age");
32console.log(`Age: ${triceratopsAge} years`); // TypeScript knows this is a number
33
34const traits = getDinosaurProperty(triceratops, "traits");
35console.log(`Traits: ${traits.join(", ")}`); // TypeScript knows this is string[]
36
37// This would not work because "nonExistentProperty" is not a key of DinosaurData
38// getDinosaurProperty(triceratops, "nonExistentProperty");

Les paramètres de type par défaut

Tout comme les fonctions peuvent avoir des valeurs par défaut pour leurs paramètres, les fonctions generic peuvent avoir des types par défaut pour leurs paramètres de type :

1// Generic function with default type parameter
2function createDinosaurReport<T = DinosaurData>(data: T, label: string = "Standard report"): { data: T, label: string, timestamp: Date } {
3  return {
4    data,
5    label,
6    timestamp: new Date()
7  };
8}
9
10// Using with default type
11const standardReport = createDinosaurReport(triceratops);
12console.log(`Report: ${standardReport.label}`);
13console.log(`Data for: ${standardReport.data.name}`);
14
15// Using with a different type
16interface ClimateData {
17  temperature: number;
18  humidity: number;
19  pressure: number;
20  location: string;
21}
22
23const enclosureClimateData: ClimateData = {
24  temperature: 28.5,
25  humidity: 72,
26  pressure: 1013,
27  location: "Triceratops Enclosure"
28};
29
30const climateReport = createDinosaurReport<ClimateData>(
31  enclosureClimateData,
32  "Climate report"
33);
34
35console.log(`Report: ${climateReport.label}`);
36console.log(`Temperature: ${climateReport.data.temperature}°C`);

Opérations generic sur les tableaux et les collections

Les fonctions generic sont particulièrement utiles pour travailler avec des tableaux et des collections de données :

1// Generic function for filtering an array
2function filterCollection<T>(collection: T[], predicate: (element: T) => boolean): T[] {
3  return collection.filter(predicate);
4}
5
6// Sample dinosaur data
7interface DinosaurBriefInfo {
8  id: string;
9  name: string;
10  species: string;
11  predator: boolean;
12  weight: number;
13}
14
15const dinosaurs: DinosaurBriefInfo[] = [
16  { id: "TREX-001", name: "Rex", species: "Tyrannosaurus", predator: true, weight: 8000 },
17  { id: "VEL-001", name: "Blue", species: "Velociraptor", predator: true, weight: 150 },
18  { id: "TRIC-001", name: "Tricky", species: "Triceratops", predator: false, weight: 9000 },
19  { id: "STEG-001", name: "Spike", species: "Stegosaurus", predator: false, weight: 5000 },
20  { id: "BRACH-001", name: "Brachie", species: "Brachiosaurus", predator: false, weight: 40000 }
21];
22
23// Filtering predatory dinosaurs
24const predators = filterCollection<DinosaurBriefInfo>(
25  dinosaurs,
26  dino => dino.predator
27);
28
29console.log("Predators in the park:");
30predators.forEach(dino => console.log(`- ${dino.name} (${dino.species})`));
31
32// Filtering dinosaurs heavier than 10000 kg
33const heavyDinosaurs = filterCollection<DinosaurBriefInfo>(
34  dinosaurs,
35  dino => dino.weight > 10000
36);
37
38console.log("\nHeavy dinosaurs:");
39heavyDinosaurs.forEach(dino => console.log(`- ${dino.name}: ${dino.weight} kg`));
40
41// More advanced generic function for mapping an array
42function transformCollection<T, U>(collection: T[], transform: (element: T) => U): U[] {
43  return collection.map(transform);
44}
45
46// Creating simpler objects from dinosaurs
47const dinosaurLabels = transformCollection<DinosaurBriefInfo, { name: string, type: string }>(
48  dinosaurs,
49  dino => ({
50    name: dino.name,
51    type: dino.predator ? "predator" : "herbivore"
52  })
53);
54
55console.log("\nDinosaur labels:");
56dinosaurLabels.forEach(label => console.log(`- ${label.name}: ${label.type}`));

Les classes et méthodes generic

Les fonctions ne sont pas les seules à pouvoir être generic : les classes et leurs méthodes peuvent elles aussi tirer parti de cette fonctionnalité puissante :

1// Generic Collection class - can store data of any type
2class Collection<T> {
3  private elements: T[] = [];
4
5  // Method adding an element to the collection
6  add(element: T): void {
7    this.elements.push(element);
8  }
9
10  // Method retrieving an element at a given index
11  get(index: number): T | undefined {
12    if (index >= 0 && index < this.elements.length) {
13      return this.elements[index];
14    }
15    return undefined;
16  }
17
18  // Method returning all elements
19  getAll(): T[] {
20    return [...this.elements];
21  }
22
23  // Method filtering elements
24  filter(predicate: (element: T) => boolean): T[] {
25    return this.elements.filter(predicate);
26  }
27
28  // Generic method mapping to another type
29  map<U>(transform: (element: T) => U): U[] {
30    return this.elements.map(transform);
31  }
32
33  // Method returning the number of elements
34  itemsCount(): number {
35    return this.elements.length;
36  }
37}
38
39// Example usage with dinosaurs
40const dinosaursCollection = new Collection<DinosaurBriefInfo>();
41
42// Adding dinosaurs to the collection
43dinosaurs.forEach(dino => dinosaursCollection.add(dino));
44
45// Getting a specific dinosaur
46const first = dinosaursCollection.get(0);
47if (first) {
48  console.log(`First dinosaur: ${first.name} (${first.species})`);
49}
50
51// Filtering the collection
52const smallDinosaurs = dinosaursCollection.filter(dino => dino.weight < 1000);
53console.log(`Number of small dinosaurs: ${smallDinosaurs.length}`);
54
55// Mapping to health reports
56interface HealthReport {
57  id: string;
58  name: string;
59  healthStatus: string;
60  notes: string;
61}
62
63const healthReports = dinosaursCollection.map<HealthReport>(dino => ({
64  id: dino.id,
65  name: dino.name,
66  healthStatus: "good",
67  notes: dino.predator
68    ? "Exercise special caution"
69    : "Standard procedures"
70}));
71
72console.log("\nHealth reports:");
73healthReports.forEach(report => {
74  console.log(`- ${report.name}: ${report.healthStatus} (${report.notes})`);
75});

Les interfaces et types generic

Tout comme les fonctions et les classes, les interfaces et les types peuvent eux aussi être generic :

1// Generic interface for a storage system
2interface System<T> {
3  add(element: T): void;
4  get(id: string): T | null;
5  update(id: string, element: T): boolean;
6  remove(id: string): boolean;
7  getAll(): T[];
8}
9
10// Implementation of a generic storage system for dinosaurs
11class DinosaursDatabase<T extends { id: string }> implements System<T> {
12  private data: Map<string, T> = new Map();
13
14  add(element: T): void {
15    this.data.set(element.id, element);
16  }
17
18  get(id: string): T | null {
19    return this.data.has(id) ? this.data.get(id)! : null;
20  }
21
22  update(id: string, element: T): boolean {
23    if (this.data.has(id)) {
24      this.data.set(id, element);
25      return true;
26    }
27    return false;
28  }
29
30  remove(id: string): boolean {
31    return this.data.delete(id);
32  }
33
34  getAll(): T[] {
35    return Array.from(this.data.values());
36  }
37}
38
39// Defining types using generic type
40type WithAssignedKeeper<T> = {
41  data: T;
42  assignedEmployee: string;
43  lastUpdated: Date;
44};
45
46// Using the generic type
47type DinosaurWithKeeper = WithAssignedKeeper<DinosaurBriefInfo>;
48
49// Creating a database for dinosaurs with assigned keepers
50const databaseWithKeepers = new DinosaursDatabase<DinosaurWithKeeper>();
51
52// Adding records
53databaseWithKeepers.add({
54  data: dinosaurs[0],
55  assignedEmployee: "Owen Grady",
56  lastUpdated: new Date(),
57  id: dinosaurs[0].id
58});
59
60databaseWithKeepers.add({
61  data: dinosaurs[1],
62  assignedEmployee: "Dr. Henry Wu",
63  lastUpdated: new Date(),
64  id: dinosaurs[1].id
65});
66
67// Retrieving data
68const rexWithKeeper = databaseWithKeepers.get("TREX-001");
69if (rexWithKeeper) {
70  console.log(`\nDinosaur ${rexWithKeeper.data.name} is under the care of ${rexWithKeeper.assignedEmployee}`);
71  console.log(`Last updated: ${rexWithKeeper.lastUpdated.toLocaleDateString()}`);
72}

Exemple pratique : le système de gestion du parc

Regardons un exemple complet montrant comment les fonctions generic peuvent être utilisées dans le système de gestion de Jurassic Park :

1// ===== JURASSIC PARK MANAGEMENT SYSTEM =====
2
3// Base interfaces
4interface ParkElement {
5  id: string;
6  name: string;
7  elementType: string;
8}
9
10interface ParkArea extends ParkElement {
11  elementType: "area";
12  coordinates: [number, number];
13  area: number; // in hectares
14  securityLevel: 1 | 2 | 3 | 4 | 5;
15}
16
17interface DinosaurEnclosure extends ParkArea {
18  elementType: "enclosure";
19  residents: string[]; // Dinosaur IDs
20  environmentType: "forest" | "savanna" | "swamp" | "coast" | "water";
21  fenceStatus: "active" | "inactive" | "under repair";
22}
23
24interface ParkDinosaur extends ParkElement {
25  elementType: "dinosaur";
26  species: string;
27  age: number;
28  weight: number;
29  predator: boolean;
30  location: string; // Area ID
31  healthStatus: "excellent" | "good" | "needs attention" | "sick" | "critical";
32}
33
34interface ParkEmployee extends ParkElement {
35  elementType: "employee";
36  position: string;
37  accessLevel: 1 | 2 | 3 | 4 | 5;
38  specializations: string[];
39}
40
41// Generic repository class
42class Repository<T extends ParkElement> {
43  private elements: Map<string, T> = new Map();
44
45  add(element: T): void {
46    this.elements.set(element.id, element);
47  }
48
49  get(id: string): T | undefined {
50    return this.elements.get(id);
51  }
52
53  getAll(): T[] {
54    return Array.from(this.elements.values());
55  }
56
57  update(id: string, data: Partial<T>): boolean {
58    if (this.elements.has(id)) {
59      const element = this.elements.get(id)!;
60      this.elements.set(id, { ...element, ...data });
61      return true;
62    }
63    return false;
64  }
65
66  remove(id: string): boolean {
67    return this.elements.delete(id);
68  }
69
70  filter(predicate: (element: T) => boolean): T[] {
71    return this.getAll().filter(predicate);
72  }
73}
74
75// Generic function for finding relationships between elements
76function findAssociations<T extends ParkElement, U extends ParkElement>(
77  sources: T[],
78  targets: U[],
79  predicate: (source: T, target: U) => boolean
80): Array<[T, U[]]> {
81  return sources.map(source => {
82    const relatedTargets = targets.filter(target => predicate(source, target));
83    return [source, relatedTargets] as [T, U[]];
84  });
85}
86
87// Generic function for generating reports
88function generateReport<T extends ParkElement>(
89  name: string,
90  elements: T[],
91  formatElement: (element: T) => string
92): string {
93  let report = `=== ${name} ===\n`;
94  report += `Generated on: ${new Date().toLocaleString()}\n`;
95  report += `Number of elements: ${elements.length}\n\n`;
96
97  if (elements.length === 0) {
98    report += "No data to display.\n";
99  } else {
100    elements.forEach(element => {
101      report += formatElement(element) + "\n";
102    });
103  }
104
105  return report;
106}
107
108// Simulating park data
109// Creating repositories
110const repoDinosaurs = new Repository<ParkDinosaur>();
111const areasRepo = new Repository<ParkArea>();
112const enclosuresRepo = new Repository<DinosaurEnclosure>();
113const employeesRepo = new Repository<ParkEmployee>();
114
115// Adding data to repositories
116// Areas
117const centralArea: ParkArea = {
118  id: "OBS-001",
119  name: "Central Area",
120  elementType: "area",
121  coordinates: [20.123, -75.456],
122  area: 25,
123  securityLevel: 1
124};
125areasRepo.add(centralArea);
126
127// Enclosures
128const tRexEnclosure: DinosaurEnclosure = {
129  id: "WYB-001",
130  name: "T-Rex Enclosure",
131  elementType: "enclosure",
132  coordinates: [20.145, -75.460],
133  area: 8,
134  securityLevel: 5,
135  residents: ["DINO-001"],
136  environmentType: "forest",
137  fenceStatus: "active"
138};
139enclosuresRepo.add(tRexEnclosure);
140
141const raptorEnclosure: DinosaurEnclosure = {
142  id: "WYB-002",
143  name: "Raptor Enclosure",
144  elementType: "enclosure",
145  coordinates: [20.150, -75.465],
146  area: 5,
147  securityLevel: 5,
148  residents: ["DINO-002", "DINO-003", "DINO-004", "DINO-005"],
149  environmentType: "savanna",
150  fenceStatus: "active"
151};
152enclosuresRepo.add(raptorEnclosure);
153
154// Dinosaurs
155const trex: ParkDinosaur = {
156  id: "DINO-001",
157  name: "Rexy",
158  elementType: "dinosaur",
159  species: "Tyrannosaurus Rex",
160  age: 7,
161  weight: 8000,
162  predator: true,
163  location: "WYB-001",
164  healthStatus: "good"
165};
166repoDinosaurs.add(trex);
167
168const raptor1: ParkDinosaur = {
169  id: "DINO-002",
170  name: "Blue",
171  elementType: "dinosaur",
172  species: "Velociraptor",
173  age: 4,
174  weight: 150,
175  predator: true,
176  location: "WYB-002",
177  healthStatus: "excellent"
178};
179repoDinosaurs.add(raptor1);
180
181// Employees
182const trainer: ParkEmployee = {
183  id: "PRAC-001",
184  name: "Owen Grady",
185  elementType: "employee",
186  position: "Raptor Trainer",
187  accessLevel: 4,
188  specializations: ["Behaviorism", "Dinosaur training", "Field operations"]
189};
190employeesRepo.add(trainer);
191
192const scientist: ParkEmployee = {
193  id: "PRAC-002",
194  name: "Dr. Henry Wu",
195  elementType: "employee",
196  position: "Chief Geneticist",
197  accessLevel: 5,
198  specializations: ["Genetics", "Genetic engineering", "Cloning"]
199};
200employeesRepo.add(scientist);
201
202// Sample use of generic functions
203
204// 1. Finding dinosaurs in specific enclosures
205const dinosaursInEnclosures = findAssociations<DinosaurEnclosure, ParkDinosaur>(
206  enclosuresRepo.getAll(),
207  repoDinosaurs.getAll(),
208  (enclosure, dino) => enclosure.residents.includes(dino.id)
209);
210
211// 2. Generating enclosure status report
212const enclosuresReport = generateReport<DinosaurEnclosure>(
213  "ENCLOSURE STATUS REPORT",
214  enclosuresRepo.getAll(),
215  enclosure => {
216    const residentsCount = enclosure.residents.length;
217    return `${enclosure.name} (ID: ${enclosure.id})
218    - Environment type: ${enclosure.environmentType}
219    - Area: ${enclosure.area} ha
220    - Fence status: ${enclosure.fenceStatus}
221    - Security level: ${enclosure.securityLevel}
222    - Number of inhabitants: ${residentsCount}`;
223  }
224);
225
226// 3. Generating dinosaur health report
227const dinosaursHealthReport = generateReport<ParkDinosaur>(
228  "DINOSAUR HEALTH REPORT",
229  repoDinosaurs.getAll(),
230  dino => {
231    const location = enclosuresRepo.get(dino.location)?.name || "Unknown";
232    return `${dino.name} (${dino.species})
233    - ID: ${dino.id}
234    - Age: ${dino.age} years
235    - Weight: ${dino.weight} kg
236    - Health status: ${dino.healthStatus}
237    - Location: ${location}`;
238  }
239);
240
241// Displaying reports
242console.log("=== DINOSAURS IN ENCLOSURES ===");
243dinosaursInEnclosures.forEach(([enclosure, dinosaurs]) => {
244  console.log(`Enclosure: ${enclosure.name}`);
245  if (dinosaurs.length === 0) {
246    console.log("  No inhabitants");
247  } else {
248    dinosaurs.forEach(dino => {
249      console.log(`  - ${dino.name} (${dino.species}): ${dino.healthStatus}`);
250    });
251  }
252});
253
254console.log("\n" + enclosuresReport);
255console.log("\n" + dinosaursHealthReport);

Résumé

Les fonctions generic sont un outil extrêmement puissant en TypeScript, qui permet de créer des composants flexibles et réutilisables tout en conservant une sécurité de types complète. Tout comme les techniques génétiques avancées du Dr Wu, les fonctions generic permettent de créer des solutions hautement adaptatives capables de fonctionner avec différents types de données.

TechniqueUtilisation à Jurassic Park
Fonctions generic de baseTraitement de différents types de données de dinosaures
Fonctions generic à plusieurs paramètresCombinaison de données de dinosaures provenant de sources différentes
Contraintes genericS'assurer que les fonctions ne fonctionnent qu'avec des types de dinosaures adéquats
Contraintes keyofAccès sécurisé aux propriétés des dinosaures
Paramètres de type par défautSimplifier le travail avec les types de données les plus utilisés
Opérations generic sur les collectionsGérer efficacement des groupes de dinosaures
Classes et méthodes genericCréer des systèmes flexibles de gestion des spécimens
Interfaces et types genericDéfinir des contrats cohérents pour les différents sous-systèmes du parc

Le Dr Wu résume ainsi : « Tout comme nos techniques génétiques révolutionnaires nous ont permis de ramener à la vie des espèces disparues de la surface de la Terre depuis 65 millions d'années, les fonctions generic de TypeScript permettent aux développeurs de créer des solutions flexibles et adaptatives, capables d'évoluer avec les besoins du système. Souviens-toi cependant qu'un grand pouvoir implique de grandes responsabilités, aussi bien en ingénierie génétique qu'en programmation. »

Code de cette leçon : index.ts
1// Functions génériques dans le Parc Jurassique
2console.log("Functions génériques TypeScript\n");
3
4// ===========================================
5// 1. Functions génériques de base
6// ===========================================
7console.log("=== GENERICS DE BASE ===");
8
9// Function générique simple
10function identity<T>(value: T): T {
11  return value;
12}
13
14const stringValue = identity<string>("Tyrannosaurus Rex");
15const numberValue = identity<number>(8000);
16const boolValue = identity<boolean>(true);
17
18console.log("String:", stringValue);
19console.log("Number:", numberValue);
20console.log("Boolean:", boolValue);
21
22// Type inference - TypeScript infère le type
23const autoString = identity("Auto string"); // T = string
24const autoNumber = identity(42); // T = number
25
26console.log("Auto string:", autoString);
27console.log("Auto number:", autoNumber);
28
29// ===========================================
30// 2. Generics avec des tableaux
31// ===========================================
32console.log("\n=== GENERICS AVEC DES TABLEAUX ===");
33
34function getFirst<T>(array: T[]): T | undefined {
35  return array[0];
36}
37
38const dinosaurs = ["T-Rex", "Velociraptor", "Triceratops"];
39const weights = [8000, 350, 5000];
40
41console.log("First dinosaur:", getFirst(dinosaurs));
42console.log("First weight:", getFirst(weights));
43
44function getLast<T>(array: T[]): T | undefined {
45  return array[array.length - 1];
46}
47
48console.log("Last dinosaur:", getLast(dinosaurs));
49
50// ===========================================
51// 3. Generics avec plusieurs types
52// ===========================================
53console.log("\n=== MULTIPLE GENERIC TYPES ===");
54
55function createPair<K, V>(key: K, value: V): { key: K; value: V } {
56  return { key, value };
57}
58
59const pair1 = createPair<string, number>("age", 5);
60const pair2 = createPair<number, string>(1, "Rexy");
61const pair3 = createPair("weight", 8000); // type inference
62
63console.log("Pair 1:", pair1);
64console.log("Pair 2:", pair2);
65console.log("Pair 3:", pair3);
66
67// ===========================================
68// 4. Generic constraints
69// ===========================================
70console.log("\n=== GENERIC CONSTRAINTS ===");
71
72interface HasName {
73  name: string;
74}
75
76// T must have property 'name'
77function logName<T extends HasName>(entity: T): T {
78  console.log(`Name: ${entity.name}`);
79  return entity;
80}
81
82interface Dinosaur {
83  name: string;
84  species: string;
85}
86
87const trex: Dinosaur = {
88  name: "Rexy",
89  species: "Tyrannosaurus Rex"
90};
91
92logName(trex);
93// logName({ age: 5 }); // Error: no 'name' property
94
95// Constraint z extends
96function getLength<T extends { length: number }>(value: T): number {
97  return value.length;
98}
99
100console.log("String length:", getLength("Hello"));
101console.log("Array length:", getLength([1, 2, 3]));
102// console.log(getLength(123)); // Error: no 'length' property
103
104// ===========================================
105// 5. Generic avec des interfaces
106// ===========================================
107console.log("\n=== GENERICS AVEC DES INTERFACES ===");
108
109interface Container<T> {
110  value: T;
111  getValue(): T;
112  setValue(value: T): void;
113}
114
115class DinosaurContainer implements Container<Dinosaur> {
116  value: Dinosaur;
117
118  constructor(value: Dinosaur) {
119    this.value = value;
120  }
121
122  getValue(): Dinosaur {
123    return this.value;
124  }
125
126  setValue(value: Dinosaur): void {
127    this.value = value;
128  }
129}
130
131const dinoContainer = new DinosaurContainer(trex);
132console.log("Container value:", dinoContainer.getValue());
133
134// ===========================================
135// 6. Generic Repository Pattern
136// ===========================================
137console.log("\n=== REPOSITORY PATTERN ===");
138
139interface Repository<T> {
140  items: T[];
141  add(item: T): void;
142  remove(item: T): void;
143  find(predicate: (item: T) => boolean): T | undefined;
144  getAll(): T[];
145}
146
147class DinosaurRepository implements Repository<Dinosaur> {
148  items: Dinosaur[] = [];
149
150  add(dinosaur: Dinosaur): void {
151    this.items.push(dinosaur);
152    console.log(`Added ${dinosaur.name} to repository`);
153  }
154
155  remove(dinosaur: Dinosaur): void {
156    const index = this.items.findIndex(d => d.name === dinosaur.name);
157    if (index !== -1) {
158      this.items.splice(index, 1);
159      console.log(`Removed ${dinosaur.name} from repository`);
160    }
161  }
162
163  find(predicate: (dinosaur: Dinosaur) => boolean): Dinosaur | undefined {
164    return this.items.find(predicate);
165  }
166
167  getAll(): Dinosaur[] {
168    return [...this.items];
169  }
170}
171
172const dinoRepo = new DinosaurRepository();
173dinoRepo.add(trex);
174dinoRepo.add({ name: "Blue", species: "Velociraptor" });
175
176const foundDino = dinoRepo.find(d => d.name === "Blue");
177console.log("Found:", foundDino);
178
179console.log("All dinosaurs:", dinoRepo.getAll().map(d => d.name));
180
181// ===========================================
182// 7. Generic functions Array
183// ===========================================
184console.log("\n=== GENERIC ARRAY FUNCTIONS ===");
185
186function map<T, U>(array: T[], fn: (item: T) => U): U[] {
187  const result: U[] = [];
188  for (const item of array) {
189    result.push(fn(item));
190  }
191  return result;
192}
193
194const dinoNames = ["Rexy", "Blue", "Charlie"];
195const upperNames = map(dinoNames, name => name.toUpperCase());
196const nameLengths = map(dinoNames, name => name.length);
197
198console.log("Upper names:", upperNames);
199console.log("Name lengths:", nameLengths);
200
201function filter<T>(array: T[], predicate: (item: T) => boolean): T[] {
202  const result: T[] = [];
203  for (const item of array) {
204    if (predicate(item)) {
205      result.push(item);
206    }
207  }
208  return result;
209}
210
211const longNames = filter(dinoNames, name => name.length > 4);
212console.log("Long names:", longNames);
213
214// ===========================================
215// 8. Generic Promise
216// ===========================================
217console.log("\n=== GENERIC PROMISE ===");
218
219function fetchDinosaur(id: number): Promise<Dinosaur> {
220  return new Promise((resolve) => {
221    setTimeout(() => {
222      resolve({
223        name: `Dinosaur-${id}`,
224        species: "Unknown"
225      });
226    }, 100);
227  });
228}
229
230async function getDinosaurData(): Promise<void> {
231  const dino = await fetchDinosaur(1);
232  console.log("Fetched dinosaur:", dino);
233}
234
235getDinosaurData();
236
237// ===========================================
238// 9. Generic Default Types
239// ===========================================
240console.log("\n=== DEFAULT TYPES ===");
241
242interface Response<T = any> {
243  status: number;
244  data: T;
245  message: string;
246}
247
248const stringResponse: Response<string> = {
249  status: 200,
250  data: "Success",
251  message: "OK"
252};
253
254const dinoResponse: Response<Dinosaur> = {
255  status: 200,
256  data: trex,
257  message: "Dinosaur found"
258};
259
260const defaultResponse: Response = {
261  status: 404,
262  data: null,
263  message: "Not found"
264};
265
266console.log("String response:", stringResponse);
267console.log("Dino response:", dinoResponse.data.name);
268
269// ===========================================
270// 10. Keyof operator avec Generic
271// ===========================================
272console.log("\n=== KEYOF OPERATOR ===");
273
274function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
275  return obj[key];
276}
277
278const blue: Dinosaur = {
279  name: "Blue",
280  species: "Velociraptor"
281};
282
283const dinoName = getProperty(blue, "name"); // string
284const dinoSpecies = getProperty(blue, "species"); // string
285// const invalid = getProperty(blue, "age"); // Error
286
287console.log("Property 'name':", dinoName);
288console.log("Property 'species':", dinoSpecies);
289
290// ===========================================
291// 11. Generic Utility Functions
292// ===========================================
293console.log("\n=== UTILITY FUNCTIONS ===");
294
295function merge<T, U>(obj1: T, obj2: U): T & U {
296  return { ...obj1, ...obj2 };
297}
298
299const basicInfo = { name: "Rexy", age: 5 };
300const detailedInfo = { species: "T-Rex", weight: 8000 };
301
302const fullInfo = merge(basicInfo, detailedInfo);
303console.log("Merged:", fullInfo);
304
305function partial<T>(obj: T): Partial<T> {
306  return { ...obj };
307}
308
309const partialDino = partial(trex);
310console.log("Partial:", partialDino);
311
312// ===========================================
313// 12. Résumé
314// ===========================================
315console.log("\n=== RÉSUMÉ ===");
316console.log("Functions génériques - <T>");
317console.log("Multiple types - <K, V>");
318console.log("Constraints - <T extends Type>");
319console.log("Generic interfaces - Interface<T>");
320console.log("Repository pattern - repository universel");
321console.log("Generic Promise - types asynchrones");
322console.log("Keyof operator - access to keys");
323console.log("Utility functions - universal tools");
324
325console.log("\nFunctions génériques maîtrisées !");

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Qu’est-ce qu’une fonction générique en TypeScript ?

  2. 2. Comment contraindre un paramètre générique T à n’accepter que des types possédant une propriété 'id' ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Implémente une fonction générique findById<T> qui prend un tableau d’objets avec un champ id et retourne un élément ou undefined.

  • Éditeur de code

    Implémente une fonction générique filterByProperty<T, K> qui prend un tableau, une clé et une valeur, et retourne les éléments filtrés.

  • Clique dans l'ordre

    Ordonne les éléments d’une fonction générique TypeScript :

  • Tri vertical

    Ordonne les concepts génériques du plus simple au plus avancé :

Articles utiles