Cours JavaScript et TypeScript · Module 7: TypeScript avancé
Types conditionnels
Dans cette leçon8
« Dr Wu, nous avons une situation », dit un assistant de laboratoire nerveux. « Le patron d'ADN que nous essayons de séquencer pourrait provenir d'une espèce de dinosaure prédatrice ou herbivore, mais nous avons besoin de protocoles de traitement différents selon le cas. »
Le Dr Henry Wu, généticien en chef de Jurassic Park, répond calmement : « Ce n'est pas un problème. Nos algorithmes de séquençage sont assez intelligents pour détecter les marqueurs de prédation dans l'ADN et appliquer automatiquement le protocole approprié. C'est ça, la flexibilité de notre système. »
Tout comme les algorithmes avancés de séquençage de l'ADN à Jurassic Park, TypeScript dispose d'un mécanisme appelé types conditionnels qui permet de déterminer dynamiquement des types en fonction d'autres types. Cet outil permet de créer des API plus flexibles et plus précises qui s'adaptent à différents scénarios.
Les bases des types conditionnels
Les types conditionnels en TypeScript fonctionnent de façon similaire aux expressions conditionnelles en JavaScript, mais au niveau des types. Leur syntaxe rappelle l'opérateur ternaire :
1T extends U ? X : YCela se lit ainsi : « Si le type T étend (est compatible avec) le type U, alors le résultat est le type X, sinon le type Y ».
Exemple simple
Créons une fonction simple qui traite un objet représentant un dinosaure et renvoie des résultats différents selon son régime alimentaire :
1type Carnivore = { type: "carnivore"; huntingSkill: number };
2type Herbivore = { type: "herbivore"; foragingSkill: number };
3
4type Dinosaur = Carnivore | Herbivore;
5
6// Conditional type determining the appropriate result type
7type DinosaurActivity<T extends Dinosaur> = T extends Carnivore
8 ? { activity: "hunting"; preyTargeted: string }
9 : { activity: "grazing"; plantsConsumed: number };
10
11// Function using the conditional type
12function trackDinosaurActivity<T extends Dinosaur>(dino: T): DinosaurActivity<T> {
13 if (dino.type === "carnivore") {
14 // TypeScript knows that for Carnivore we should return { activity: "hunting"; preyTargeted: string }
15 return {
16 activity: "hunting",
17 preyTargeted: "Gallimimus"
18 } as DinosaurActivity<T>;
19 } else {
20 // TypeScript knows that for Herbivore we should return { activity: "grazing"; plantsConsumed: number }
21 return {
22 activity: "grazing",
23 plantsConsumed: 42
24 } as DinosaurActivity<T>;
25 }
26}
27
28// Example usage
29const trex: Carnivore = { type: "carnivore", huntingSkill: 10 };
30const triceratops: Herbivore = { type: "herbivore", foragingSkill: 8 };
31
32const trexActivity = trackDinosaurActivity(trex);
33console.log(trexActivity.activity); // "hunting"
34console.log(trexActivity.preyTargeted); // "Gallimimus"
35
36const triceratopsActivity = trackDinosaurActivity(triceratops);
37console.log(triceratopsActivity.activity); // "grazing"
38console.log(triceratopsActivity.plantsConsumed); // 42Dans cet exemple, le type DinosaurActivity<T> change selon que T étend Carnivore ou non. Cela nous permet de renvoyer des structures de données différentes, et TypeScript fournit un support de type complet pour les deux cas.
Types conditionnels distributifs
L'une des caractéristiques clés des types conditionnels est leur comportement distributif avec les types union. Lorsqu'on applique un type conditionnel à un type union, la condition est appliquée séparément à chaque élément de l'union.
1type ToArray<T> = T extends any ? T[] : never;
2
3// With a simple type, the result is simple
4type NumberArray = ToArray<number>; // number[]
5
6// With a union type, the condition is applied distributively
7type NumberOrStringArray = ToArray<number | string>; // number[] | string[]Dans le second cas, ToArray<number | string> équivaut à ToArray<number> | ToArray<string>, ce qui donne number[] | string[], et non (number | string)[].
On peut appliquer cela à notre exemple de dinosaure :
1// Defining more dinosaur types
2type TRex = { species: "tyrannosaurus"; strength: number };
3type Velociraptor = { species: "velociraptor"; speed: number };
4type Triceratops = { species: "triceratops"; armorLevel: number };
5type Brachiosaurus = { species: "brachiosaurus"; height: number };
6
7// Conditional type filtering predators
8type IsPredator<T> = T extends { species: "tyrannosaurus" | "velociraptor" } ? T : never;
9
10// Distributive application on union types
11type Predators = IsPredator<TRex | Velociraptor | Triceratops | Brachiosaurus>;
12// Result: TRex | Velociraptor
13
14// We can also filter herbivores
15type IsHerbivore<T> = T extends { species: "triceratops" | "brachiosaurus" } ? T : never;
16type Herbivores = IsHerbivore<TRex | Velociraptor | Triceratops | Brachiosaurus>;
17// Result: Triceratops | BrachiosaurusTypes inférés
TypeScript permet d'extraire (d'inférer) des types depuis d'autres types à l'aide du mot-clé infer. C'est particulièrement utile dans les types conditionnels.
1// Type that extracts the return type of a function
2type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
3
4// Example usage
5function studyDinosaur(specimen: string): { species: string; age: number } {
6 return {
7 species: "Velociraptor",
8 age: 5
9 };
10}
11
12// Extracted return type of the studyDinosaur function
13type StudyResult = ReturnType<typeof studyDinosaur>; // { species: string; age: number }Dans cet exemple, infer R indique à TypeScript de « déduire » le type de retour de la fonction et de l'assigner au type R, qui est ensuite utilisé comme résultat du type conditionnel.
On peut créer des exemples d'extraction de type plus complexes, par exemple :
1// Type representing dinosaur DNA
2type DinosaurDNA = {
3 species: string;
4 genomeSequence: string;
5 markers: {
6 predatory?: boolean;
7 aquatic?: boolean;
8 flying?: boolean;
9 size: "small" | "medium" | "large";
10 };
11};
12
13// Extracting markers from DNA
14type ExtractMarkers<T> = T extends { markers: infer M } ? M : never;
15
16// Extracting size from markers
17type ExtractSize<T> = T extends { size: infer S } ? S : never;
18
19// Example usage
20type DinosaurMarkers = ExtractMarkers<DinosaurDNA>;
21// { predatory?: boolean; aquatic?: boolean; flying?: boolean; size: "small" | "medium" | "large"; }
22
23type DinosaurSize = ExtractSize<DinosaurMarkers>;
24// "small" | "medium" | "large"Applications pratiques des types conditionnels
Les types conditionnels sont particulièrement utiles dans des scénarios de programmation avancés, tels que :
1. Créer des composants d'API flexibles
1// Example API for dinosaur operations
2interface DinosaurOperations<T> {
3 create: (data: T) => Promise<T>;
4 update: (id: string, data: Partial<T>) => Promise<T>;
5 delete: (id: string) => Promise<void>;
6
7 // Method that returns different types depending on parameter T
8 analyze: T extends { genomeSequence: string }
9 ? (id: string) => Promise<{ compatibilityScore: number }>
10 : (id: string) => Promise<{ behaviorPrediction: string }>;
11}
12
13// Implementation for dinosaurs with genome sequence
14interface GenomeSequencedDino {
15 id: string;
16 name: string;
17 genomeSequence: string;
18}
19
20// Implementation for dinosaurs without genome sequence
21interface BehaviorStudiedDino {
22 id: string;
23 name: string;
24 observedBehaviors: string[];
25}
26
27// Different implementations for different types
28function createDinosaurAPI<T extends GenomeSequencedDino | BehaviorStudiedDino>(
29 baseUrl: string
30): DinosaurOperations<T> {
31 return {
32 create: (data: T) => fetch(`${baseUrl}/create`, {
33 method: 'POST',
34 body: JSON.stringify(data)
35 }).then(res => res.json()),
36
37 update: (id: string, data: Partial<T>) => fetch(`${baseUrl}/${id}`, {
38 method: 'PUT',
39 body: JSON.stringify(data)
40 }).then(res => res.json()),
41
42 delete: (id: string) => fetch(`${baseUrl}/${id}`, {
43 method: 'DELETE'
44 }).then(() => undefined),
45
46 // The analyze method implementation will differ depending on type T
47 analyze: ((id: string) => {
48 if ('genomeSequence' in { ...({} as T) }) {
49 return fetch(`${baseUrl}/${id}/genome-analysis`)
50 .then(res => res.json());
51 } else {
52 return fetch(`${baseUrl}/${id}/behavior-analysis`)
53 .then(res => res.json());
54 }
55 }) as any // Cast needed due to TypeScript runtime limitations
56 };
57}
58
59// Using different APIs
60const genomeAPI = createDinosaurAPI<GenomeSequencedDino>('/api/genome-dinos');
61const result1 = await genomeAPI.analyze('dino-1'); // { compatibilityScore: number }
62
63const behaviorAPI = createDinosaurAPI<BehaviorStudiedDino>('/api/behavior-dinos');
64const result2 = await behaviorAPI.analyze('dino-2'); // { behaviorPrediction: string }2. Implémenter des motifs de type
1// Helper type Exclude - excludes types from a union
2type Exclude<T, U> = T extends U ? never : T;
3
4// Example: excluding dangerous dinosaurs
5type DangerousDinosaurs = "Tyrannosaurus" | "Velociraptor" | "Spinosaurus";
6type AllDinosaurs = "Tyrannosaurus" | "Triceratops" | "Velociraptor" | "Brachiosaurus" | "Stegosaurus" | "Spinosaurus";
7
8type SafeDinosaurs = Exclude<AllDinosaurs, DangerousDinosaurs>;
9// "Triceratops" | "Brachiosaurus" | "Stegosaurus"
10
11// Helper type Extract - extracts types meeting a condition
12type Extract<T, U> = T extends U ? T : never;
13
14// Example: extracting dinosaurs from the Jurassic era
15type JurassicDinosaurs = "Stegosaurus" | "Brachiosaurus" | "Allosaurus";
16type CretaceousDinosaurs = "Tyrannosaurus" | "Triceratops" | "Velociraptor";
17type AllEras = JurassicDinosaurs | CretaceousDinosaurs;
18
19type OnlyJurassic = Extract<AllEras, JurassicDinosaurs>;
20// "Stegosaurus" | "Brachiosaurus" | "Allosaurus"
21
22// Helper type NonNullable - removes null and undefined from a type
23type NonNullable<T> = T extends null | undefined ? never : T;
24
25// Example: ensuring we have non-empty dinosaur data
26type PossibleDinoData = { name: string; weight: number } | null | undefined;
27type DefiniteDinoData = NonNullable<PossibleDinoData>;
28// { name: string; weight: number }3. Créer des mappeurs de type
1// Type Mapper transforming object properties from one type to another
2type MapProperties<T, U> = {
3 [K in keyof T]: T[K] extends object ? MapProperties<T[K], U> : U;
4};
5
6// Example: mapping numeric values to strings
7type DinosaurStats = {
8 name: string;
9 physicalTraits: {
10 height: number;
11 weight: number;
12 speed: number;
13 };
14 behavioralTraits: {
15 aggression: number;
16 intelligence: number;
17 packBehavior: number;
18 };
19};
20
21// Transforming all numbers to strings for display purposes
22type DinosaurStatsForDisplay = MapProperties<DinosaurStats, string>;
23/*
24{
25 name: string;
26 physicalTraits: {
27 height: string;
28 weight: string;
29 speed: string;
30 };
31 behavioralTraits: {
32 aggression: string;
33 intelligence: string;
34 packBehavior: string;
35 };
36}
37*/Exemple avancé : système de types pour l'infrastructure de Jurassic Park
Voyons un exemple plus complexe qui utilise des types conditionnels pour modéliser différents systèmes de Jurassic Park :
1// --- Basic system types ---
2
3type SystemStatus = "online" | "offline" | "maintenance" | "error";
4
5interface BaseSystem {
6 id: string;
7 name: string;
8 status: SystemStatus;
9 lastUpdated: Date;
10}
11
12// Different types of systems in the park
13interface SecuritySystem extends BaseSystem {
14 type: "security";
15 enclosures: string[];
16 alertLevel: "low" | "medium" | "high" | "critical";
17 cameras: number;
18 motionSensors: number;
19}
20
21interface PowerSystem extends BaseSystem {
22 type: "power";
23 outputCapacity: number; // kW
24 currentLoad: number; // kW
25 backupCapacity: number; // kW
26 generators: number;
27}
28
29interface TransportSystem extends BaseSystem {
30 type: "transport";
31 vehicles: number;
32 routes: string[];
33 passengerCapacity: number;
34 automatedGuidance: boolean;
35}
36
37interface LifeSupportSystem extends BaseSystem {
38 type: "lifesupport";
39 habitats: string[];
40 environmentalControls: {
41 temperature: number; // °C
42 humidity: number; // %
43 oxygen: number; // %
44 };
45}
46
47// Union of all system types
48type ParkSystem = SecuritySystem | PowerSystem | TransportSystem | LifeSupportSystem;
49
50// --- Conditional types for monitoring data ---
51
52/**
53 * Conditional type that determines what monitoring data is relevant for each system type
54 */
55type MonitoringData<T extends ParkSystem> = T extends SecuritySystem
56 ? {
57 breachDetections: number;
58 securityFootage: { cameraId: string; timestamp: Date; events: string[] }[];
59 activeAlarms: string[];
60 }
61 : T extends PowerSystem
62 ? {
63 loadDistribution: { sector: string; load: number }[];
64 energyEfficiency: number; // %
65 estimatedBackupTime: number; // hours
66 powerFluctuations: boolean;
67 }
68 : T extends TransportSystem
69 ? {
70 activeVehicles: number;
71 passengerCount: number;
72 delayReports: { routeId: string; delay: number }[];
73 fuelConsumption: number; // liters/hour
74 }
75 : T extends LifeSupportSystem
76 ? {
77 habitatConditions: {
78 habitatId: string;
79 temperature: number;
80 humidity: number;
81 oxygenLevel: number;
82 co2Level: number;
83 }[];
84 anomalyDetections: string[];
85 resourceConsumption: { water: number; electricity: number; air: number };
86 }
87 : never; // Fallback pour les types inconnus
88
89/**
90 * Conditional type determining recommended actions for systems in different states
91 */
92type RecommendedActions<T extends ParkSystem> =
93 T extends { status: "error" }
94 ? { severity: "high"; actions: string[]; escalation: string; timeToResolve: number }
95 : T extends { status: "maintenance" }
96 ? { checklistItems: string[]; estimatedCompletionTime: Date; personnelAssigned: string[] }
97 : T extends { status: "offline" }
98 ? { restartProcedures: string[]; systemDependencies: string[]; estimatedDowntime: number }
99 : { routineChecks: string[]; nextMaintenanceDate: Date };
100
101// --- System monitoring class using conditional types ---
102
103/**
104 * Class managing system monitoring, using conditional types
105 * to ensure type correctness for different systems
106 */
107class SystemMonitor<T extends ParkSystem> {
108 private system: T;
109 private monitoringData: MonitoringData<T>;
110 private recommendedActions: RecommendedActions<T>;
111
112 constructor(system: T) {
113 this.system = system;
114
115 // Initializing monitoringData based on system type
116 this.monitoringData = this.initializeMonitoringData();
117
118 // Initializing recommendedActions based on system type and status
119 this.recommendedActions = this.initializeRecommendedActions();
120 }
121
122 // Helper method for initializing monitoring data
123 private initializeMonitoringData(): MonitoringData<T> {
124 // Implementation dependent on type T
125 if (this.system.type === "security") {
126 return {
127 breachDetections: 0,
128 securityFootage: [],
129 activeAlarms: []
130 } as MonitoringData<T>;
131 } else if (this.system.type === "power") {
132 return {
133 loadDistribution: [],
134 energyEfficiency: 95,
135 estimatedBackupTime: 48,
136 powerFluctuations: false
137 } as MonitoringData<T>;
138 } else if (this.system.type === "transport") {
139 return {
140 activeVehicles: 0,
141 passengerCount: 0,
142 delayReports: [],
143 fuelConsumption: 0
144 } as MonitoringData<T>;
145 } else if (this.system.type === "lifesupport") {
146 return {
147 habitatConditions: [],
148 anomalyDetections: [],
149 resourceConsumption: { water: 0, electricity: 0, air: 0 }
150 } as MonitoringData<T>;
151 } else {
152 throw new Error(`Unknown system type: ${(this.system as any).type}`);
153 }
154 }
155
156 // Helper method for initializing recommended actions
157 private initializeRecommendedActions(): RecommendedActions<T> {
158 if (this.system.status === "error") {
159 return {
160 severity: "high",
161 actions: ["Immediate technical intervention", "Notify management"],
162 escalation: "Technical Director",
163 timeToResolve: 60 // minutes
164 } as RecommendedActions<T>;
165 } else if (this.system.status === "maintenance") {
166 return {
167 checklistItems: ["Check wiring", "Test functionality"],
168 estimatedCompletionTime: new Date(Date.now() + 3600000), // +1 hour
169 personnelAssigned: ["Tech-1", "Tech-2"]
170 } as RecommendedActions<T>;
171 } else if (this.system.status === "offline") {
172 return {
173 restartProcedures: ["Power control", "System reset"],
174 systemDependencies: ["Main power supply"],
175 estimatedDowntime: 120 // minutes
176 } as RecommendedActions<T>;
177 } else {
178 return {
179 routineChecks: ["Daily overview", "Check logs"],
180 nextMaintenanceDate: new Date(Date.now() + 7 * 24 * 3600000) // +7 days
181 } as RecommendedActions<T>;
182 }
183 }
184
185 // Method for updating monitoring data
186 updateMonitoringData(newData: Partial<MonitoringData<T>>): void {
187 this.monitoringData = { ...this.monitoringData, ...newData };
188 console.log(`Updated monitoring data for ${this.system.name} (ID: ${this.system.id})`);
189 }
190
191 // Method for generating system report
192 generateSystemReport(): string {
193 const report = `
194SYSTEM REPORT: ${this.system.name.toUpperCase()} (ID: ${this.system.id})
195=========================================================
196Type: ${this.system.type}
197Status: ${this.system.status}
198Last update: ${this.system.lastUpdated.toLocaleString()}
199
200MONITORING DETAILS:
201${this.formatMonitoringData()}
202
203RECOMMENDED ACTIONS:
204${this.formatRecommendedActions()}
205=========================================================
206Report generated: ${new Date().toLocaleString()}
207`;
208
209 return report;
210 }
211
212 // Method for formatting monitoring data depending on system type
213 private formatMonitoringData(): string {
214 if (this.system.type === "security") {
215 const data = this.monitoringData as MonitoringData<SecuritySystem>;
216 return `
217Detected violations: ${data.breachDetections}
218Active alarms: ${data.activeAlarms.length > 0 ? data.activeAlarms.join(", ") : "None"}
219Security recordings: ${data.securityFootage.length}
220`;
221 } else if (this.system.type === "power") {
222 const data = this.monitoringData as MonitoringData<PowerSystem>;
223 return `
224Energy efficiency: ${data.energyEfficiency}%
225Estimated time on backup power: ${data.estimatedBackupTime} hours
226Power fluctuations: ${data.powerFluctuations ? "YES" : "NO"}
227Schedule loads: ${data.loadDistribution.map(d => `${d.sector}: ${d.load}kW`).join(", ")}
228`;
229 } else if (this.system.type === "transport") {
230 const data = this.monitoringData as MonitoringData<TransportSystem>;
231 return `
232Active vehicles: ${data.activeVehicles}
233Number of passengers: ${data.passengerCount}
234Fuel usage: ${data.fuelConsumption} liters/hr.
235Delay reports: ${data.delayReports.length > 0
236 ? data.delayReports.map(d => `${d.routeId}: ${d.delay}min`).join(", ")
237 : "No delays"}
238`;
239 } else if (this.system.type === "lifesupport") {
240 const data = this.monitoringData as MonitoringData<LifeSupportSystem>;
241 return `
242Habitat conditions: ${data.habitatConditions.map(h =>
243 `${h.habitatId} (Temp: ${h.temperature}°C, Humidity: ${h.humidity}%, O2: ${h.oxygenLevel}%)`).join("\n")}
244Detected anomalies: ${data.anomalyDetections.length > 0 ? data.anomalyDetections.join(", ") : "None"}
245Resource usage: Water: ${data.resourceConsumption.water}m³, Power: ${data.resourceConsumption.electricity}kWh, Air: ${data.resourceConsumption.air}m³
246`;
247 } else {
248 return `No monitoring data for this type of system.`;
249 }
250 }
251
252 // Method for formatting recommended actions depending on system status
253 private formatRecommendedActions(): string {
254 if (this.system.status === "error") {
255 const actions = this.recommendedActions as RecommendedActions<{ status: "error" }>;
256 return `
257Severity: ${actions.severity}
258Recommended actions: ${actions.actions.join(", ")}
259Escalation to: ${actions.escalation}
260Estimated repair time: ${actions.timeToResolve} minutes
261`;
262 } else if (this.system.status === "maintenance") {
263 const actions = this.recommendedActions as RecommendedActions<{ status: "maintenance" }>;
264 return `
265Items to check: ${actions.checklistItems.join(", ")}
266Estimated completion time: ${actions.estimatedCompletionTime.toLocaleString()}
267Assigned staff: ${actions.personnelAssigned.join(", ")}
268`;
269 } else if (this.system.status === "offline") {
270 const actions = this.recommendedActions as RecommendedActions<{ status: "offline" }>;
271 return `
272Restart procedures: ${actions.restartProcedures.join(", ")}
273System dependencies: ${actions.systemDependencies.join(", ")}
274Estimated downtime: ${actions.estimatedDowntime} minutes
275`;
276 } else {
277 const actions = this.recommendedActions as RecommendedActions<ParkSystem>;
278 return `
279Routine checks: ${actions.routineChecks.join(", ")}
280Next overview: ${actions.nextMaintenanceDate.toLocaleDateString()}
281`;
282 }
283 }
284
285 // Method returning recommended actions
286 getRecommendedActions(): RecommendedActions<T> {
287 return this.recommendedActions;
288 }
289
290 // Method for updating system status
291 updateSystemStatus(newStatus: SystemStatus): void {
292 (this.system as any).status = newStatus;
293 this.system.lastUpdated = new Date();
294
295 // Updating recommended actions after status change
296 this.recommendedActions = this.initializeRecommendedActions();
297
298 console.log(`Updated system status ${this.system.name} to ${newStatus}`);
299 }
300}
301
302// --- Example usage ---
303
304// Creating sample systems
305const securitySystem: SecuritySystem = {
306 id: "SEC-001",
307 name: "Main security system",
308 status: "online",
309 lastUpdated: new Date(),
310 type: "security",
311 enclosures: ["T-Rex", "Velociraptor", "Dilophosaurus"],
312 alertLevel: "low",
313 cameras: 24,
314 motionSensors: 36
315};
316
317const powerSystem: PowerSystem = {
318 id: "PWR-001",
319 name: "Main power system",
320 status: "online",
321 lastUpdated: new Date(),
322 type: "power",
323 outputCapacity: 10000,
324 currentLoad: 6500,
325 backupCapacity: 5000,
326 generators: 4
327};
328
329// Creating monitors for different systems
330const securityMonitor = new SystemMonitor<SecuritySystem>(securitySystem);
331const powerMonitor = new SystemMonitor<PowerSystem>(powerSystem);
332
333// Updating monitoring data
334securityMonitor.updateMonitoringData({
335 breachDetections: 3,
336 activeAlarms: ["Sector 7G", "Northern fence"],
337 securityFootage: [
338 { cameraId: "CAM-12", timestamp: new Date(), events: ["Motion in restricted area"] }
339 ]
340});
341
342powerMonitor.updateMonitoringData({
343 loadDistribution: [
344 { sector: "Visitor Center", load: 2000 },
345 { sector: "TRex Paddock", load: 1500 },
346 { sector: "Raptor Enclosure", load: 1800 },
347 { sector: "Labs", load: 1200 }
348 ],
349 energyEfficiency: 92,
350 powerFluctuations: true
351});
352
353// Simulating a power system failure
354powerMonitor.updateSystemStatus("error");
355
356// Generating reports
357console.log(securityMonitor.generateSystemReport());
358console.log(powerMonitor.generateSystemReport());
359
360// Checking if recommended actions are of the correct type
361const powerActions = powerMonitor.getRecommendedActions();
362console.log(`Power failure severity: ${powerActions.severity}`);
363console.log(`Time to solutions: ${powerActions.timeToResolve} minutes`);Cet exemple élaboré montre comment les types conditionnels peuvent être utilisés pour créer un système complexe qui adapte son comportement et sa structure de données selon le type d'objet avec lequel il travaille. C'est particulièrement utile pour modéliser des systèmes ayant de nombreuses variantes mais partageant certaines fonctionnalités communes.
Limites des types conditionnels
Bien que les types conditionnels soient un outil puissant, ils ont aussi quelques limites :
Capacité d'inférence limitée - TypeScript a une capacité limitée à inférer des types dans des scénarios complexes, surtout lorsque des types génériques sont utilisés conjointement avec des types conditionnels.
Effacement de type à l'exécution - comme les autres fonctionnalités de type avancées de TypeScript, les types conditionnels n'existent qu'au moment de la compilation. À l'exécution, il n'y a aucune information sur les types conditionnels, ce qui peut compliquer l'implémentation de certains motifs.
Complexité pour les débutants - les types conditionnels, surtout combinés à d'autres fonctionnalités de type avancées, peuvent être difficiles à comprendre pour les débutants en TypeScript.
Quand utiliser les types conditionnels ?
Les types conditionnels sont particulièrement utiles dans les scénarios suivants :
Créer des API flexibles - lorsque tu veux que tes fonctions ou méthodes se comportent différemment selon le type des arguments passés.
Implémenter des outils génériques avancés - lors de la construction d'outils complexes devant fonctionner avec différents types de données.
Modéliser des systèmes métier complexes - lorsqu'on a affaire à différentes variantes d'objets métier qui partagent des caractéristiques communes mais diffèrent dans les détails.
Créer des outils de transformation de type - lorsqu'il faut mapper un type vers un autre de façon conditionnelle.
Résumé
Les types conditionnels en TypeScript sont un outil avancé mais extrêmement utile qui permet de créer des API flexibles et sûres en matière de types. Tout comme les généticiens de Jurassic Park peuvent adapter leurs protocoles aux différents types d'ADN de dinosaures, les types conditionnels nous permettent d'adapter le comportement de notre code aux différents types de données tout en conservant un support complet du typage statique.
Dans les exercices suivants, nous continuerons à explorer les fonctionnalités avancées du système de types de TypeScript et nous verrons comment elles peuvent nous aider à créer un code plus sûr, plus flexible et plus facile à maintenir.
Code de cette leçon : index.ts
1// Conditional types (Conditional Types) - Parc Jurassique
2console.log("=== CONDITIONAL TYPES ===\n");
3
4// ===========================================
5// 1. Type conditionnel de base
6// ===========================================
7
8type IsCarnivore<T> = T extends { diet: "carnivore" } ? true : false;
9
10interface Carnivore {
11 name: string;
12 diet: "carnivore";
13 biteForce: number;
14}
15
16interface Herbivore {
17 name: string;
18 diet: "herbivore";
19 favoriteFood: string;
20}
21
22// Le type conditionnel se résout à la compilation
23type RexCheck = IsCarnivore<Carnivore>; // true
24type CeraCheck = IsCarnivore<Herbivore>; // false
25
26// ===========================================
27// 2. Extract et Exclude
28// ===========================================
29
30type DinosaurType = "T-Rex" | "Velociraptor" | "Triceratops" | "Brachiosaurus" | "Stegosaurus";
31
32// Extract - extraire les types correspondants
33type Predator = Extract<DinosaurType, "T-Rex" | "Velociraptor">;
34// Result: "T-Rex" | "Velociraptor"
35
36// Exclude - retirer les types correspondants
37type Gentle = Exclude<DinosaurType, "T-Rex" | "Velociraptor">;
38// Result: "Triceratops" | "Brachiosaurus" | "Stegosaurus"
39
40// ===========================================
41// 3. infer - extraire des types depuis des constructions
42// ===========================================
43
44type DinosaurData = {
45 name: string;
46 stats: { weight: number; height: number; speed: number };
47 abilities: string[];
48};
49
50// Extraire un type depuis une structure imbriquée
51type StatsType = DinosaurData["stats"]; // { weight: number; height: number; speed: number }
52
53// infer dans les types conditionnels
54type UnpackArray<T> = T extends Array<infer U> ? U : T;
55type AbilityType = UnpackArray<DinosaurData["abilities"]>; // string
56
57type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
58type ResolvedDino = UnpackPromise<Promise<DinosaurData>>; // DinosaurData
59
60// ===========================================
61// 4. ReturnType et Parameters
62// ===========================================
63
64function createDinosaur(name: string, weight: number, isDangerous: boolean) {
65 return {
66 name,
67 weight,
68 isDangerous,
69 createdAt: new Date()
70 };
71}
72
73type DinoResult = ReturnType<typeof createDinosaur>;
74type DinoParams = Parameters<typeof createDinosaur>;
75
76// ===========================================
77// 5. NonNullable
78// ===========================================
79
80type MaybeDino = string | number | null | undefined;
81type DefinitelyDino = NonNullable<MaybeDino>; // string | number
82
83// ===========================================
84// 6. Exemple pratique - système de réponses d'API
85// ===========================================
86
87type ApiResponse<T> = T extends "dinosaur"
88 ? { id: number; name: string; species: string }
89 : T extends "enclosure"
90 ? { id: number; name: string; capacity: number }
91 : T extends "staff"
92 ? { id: number; name: string; role: string }
93 : never;
94
95function getResponse<T extends "dinosaur" | "enclosure" | "staff">(
96 type: T
97): ApiResponse<T> {
98 if (type === "dinosaur") {
99 return { id: 1, name: "Rexy", species: "T-Rex" } as ApiResponse<T>;
100 } else if (type === "enclosure") {
101 return { id: 101, name: "Zone T-Rex", capacity: 2 } as ApiResponse<T>;
102 }
103 return { id: 1001, name: "Alan Grant", role: "Paléontologue" } as ApiResponse<T>;
104}
105
106// ===========================================
107// Test
108// ===========================================
109
110console.log("--- Extract / Exclude ---");
111const predator: Predator = "T-Rex";
112const gentle: Gentle = "Brachiosaurus";
113console.log("Prédateur:", predator);
114console.log("Doux:", gentle);
115
116console.log("\n--- ReturnType ---");
117const dino: DinoResult = createDinosaur("Blue", 15, true);
118console.log("Dinosaure créé:", JSON.stringify(dino, null, 2));
119
120console.log("\n--- Conditional API Response ---");
121const dinoResp = getResponse("dinosaur");
122console.log("Dinosaur:", JSON.stringify(dinoResp));
123
124const encResp = getResponse("enclosure");
125console.log("Enclos:", JSON.stringify(encResp));
126
127const staffResp = getResponse("staff");
128console.log("Employé:", JSON.stringify(staffResp));
129
130console.log("\n--- NonNullable ---");
131function processDino(value: NonNullable<MaybeDino>): void {
132 console.log("Valeur:", value, "Type:", typeof value);
133}
134processDino("Rexy");
135processDino(42);
136// processDino(null); // Error!
137
138console.log("\nConditional types = logique dynamique au niveau des types!");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. Que fait le type conditionnel T extends U ? X : Y en TypeScript ?
2. À quoi sert le mot-clé infer dans les types conditionnels ?
Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.
Tâches pratiques dans le jeu
- Éditeur de code
Crée des types conditionnels pour l'inférence dynamique de types
- Clique dans l'ordre
Range les éléments d'un type conditionnel :