Cours JavaScript et TypeScript · Module 6: Bases de TypeScript
Les types union et intersection
Dans cette leçon4
Bienvenue au laboratoire des types avancés de TypeScript au Jurassic Park ! Le Dr Henry Wu est particulièrement enthousiaste aujourd'hui, car nous allons aborder deux mécanismes puissants de TypeScript que l'on peut comparer à ses projets de génie génétique les plus ambitieux : les types union et intersection.
Les types union - « soit ceci, soit cela »
Un type union permet à une variable d'accepter des valeurs de l'un de plusieurs types différents. C'est un peu comme un enclos hybride dans le parc qui peut accueillir différentes espèces de dinosaures, mais une seule à la fois.
Syntaxe de base des types union
On crée un type union à l'aide de l'opérateur | (pipe) :
1// Une variable peut être de type string OU number
2let dinosaurIdentifier: string | number;
3
4dinosaurIdentifier = "TREX-01"; // Ok, string est autorisé
5dinosaurIdentifier = 12345; // Ok, number est autorisé
6// dinosaurIdentifier = true; // Erreur ! boolean ne fait pas partie de l'union string | numberApplications pratiques des types union
Les types union sont extrêmement utiles dans de nombreux scénarios pratiques au Jurassic Park :
1. Gérer différents formats d'identifiants
1// Fonction qui peut accepter différents types d'identifiants
2function findDinosaur(id: string | number): void {
3 // On doit vérifier le type avant d'effectuer des opérations propres à un type
4 if (typeof id === "string") {
5 console.log(`Looking for dinosaur with code: ${id.toUpperCase()}`);
6 } else {
7 console.log(`Looking for dinosaur with number: ${id.toFixed(0)}`);
8 }
9}
10
11findDinosaur("RAPTOR-BLUE"); // "Looking for dinosaur with code: RAPTOR-BLUE"
12findDinosaur(42); // "Looking for dinosaur with number: 42"2. Fonctions renvoyant différents types
1// Fonction qui peut renvoyer un dinosaure ou un message d'erreur
2function getDinosaurData(id: string):
3 { name: string; species: string; age: number } | string {
4
5 // Simulation d'une base de données
6 const database = new Map([
7 ["TRex1", { name: "Rex", species: "Tyrannosaurus", age: 7 }],
8 ["Rap1", { name: "Blue", species: "Velociraptor", age: 4 }]
9 ]);
10
11 const dinosaur = database.get(id);
12
13 if (dinosaur) {
14 return dinosaur; // Renvoie l'objet dinosaure
15 } else {
16 return `Dinosaur with ID not found: ${id}`; // Renvoie un message d'erreur
17 }
18}
19
20const result = getDinosaurData("TRex1");
21// On doit vérifier le type de la valeur renvoyée
22if (typeof result === "string") {
23 console.log(`Error: ${result}`);
24} else {
25 console.log(`Found: ${result.name}, species: ${result.species}`);
26}3. Gérer les paramètres optionnels
1// Fonction générant un rapport qui peut accepter différents paramètres de filtre
2function generateDinosaurReport(
3 filtr?: { species?: string; minAge?: number; predators?: boolean }
4): string {
5 let report = "Dinosaur report:\n";
6
7 if (filtr) {
8 report += "Filters:";
9 if (filtr.species) report += ` Species=${filtr.species}`;
10 if (filtr.minAge) report += ` MinAge=${filtr.minAge}`;
11 if (filtr.predators !== undefined) report += ` OnlyPredators=${filtr.predators}`;
12 } else {
13 report += "No filters - showing all dinosaurs";
14 }
15
16 return report;
17}
18
19console.log(generateDinosaurReport()); // Sans filtres
20console.log(generateDinosaurReport({ species: "Velociraptor" })); // Avec filtre d'espèce
21console.log(generateDinosaurReport({ minAge: 5, predators: true })); // Avec plusieurs filtresLes unions discriminées
Les unions discriminées sont une technique avancée d'utilisation des types union avec une « propriété discriminante » qui permet à TypeScript de reconnaître à quel type nous avons affaire. C'est comme le système d'identification des dinosaures du parc, qui indique immédiatement si nous avons affaire à un herbivore ou à un prédateur.
1// Définition d'interfaces avec la propriété discriminante "type"
2interface Herbivore {
3 type: "herbivore";
4 diet: string[];
5 aggressionLevel: number; // 1-10
6}
7
8interface Predator {
9 type: "predator";
10 prey: string[];
11 biteForce: number; // in PSI
12}
13
14// Type qui est une union de deux interfaces
15type Dinosaur = Herbivore | Predator;
16
17// Fonction utilisant l'union discriminée
18function manageDinosaur(dino: Dinosaur): void {
19 // On vérifie la propriété discriminante
20 switch (dino.type) {
21 case "herbivore":
22 // TypeScript sait ici que dino est de type Herbivore
23 console.log(`Prepare fodder: ${dino.diet.join(", ")}`);
24 if (dino.aggressionLevel > 7) {
25 console.log("Warning: Herbivore with high aggression level!");
26 }
27 break;
28 case "predator":
29 // TypeScript sait ici que dino est de type Predator
30 console.log(`Prepare meat for: ${dino.prey.join(", ")}`);
31 if (dino.biteForce > 10000) {
32 console.log("Warning: Very dangerous predator!");
33 }
34 break;
35 }
36}
37
38// Exemples d'utilisation
39const triceratops: Herbivore = {
40 type: "herbivore",
41 diet: ["ferns", "cycads", "conifers"],
42 aggressionLevel: 6
43};
44
45const tRex: Predator = {
46 type: "predator",
47 prey: ["triceratops", "edmontosaurus", "pachycephalosaurus"],
48 biteForce: 12800
49};
50
51manageDinosaur(triceratops);
52manageDinosaur(tRex);Les types intersection - « à la fois ceci et cela »
Un type intersection permet de combiner plusieurs types en un seul type plus complexe. C'est comme les expériences du Dr Wu qui combine l'ADN de différents dinosaures pour créer des espèces entièrement nouvelles dotées des traits de tous les « ingrédients ».
Syntaxe de base des types intersection
On crée un type intersection à l'aide de l'opérateur & :
1// Définition d'interfaces de traits de base
2interface BasicDinosaur {
3 id: number;
4 name: string;
5 species: string;
6 age: number;
7}
8
9interface PhysicalTraits {
10 height: number; // in meters
11 length: number; // in meters
12 weight: number; // in kilograms
13}
14
15interface BehavioralTraits {
16 aggressionLevel: number; // 1-10
17 territorial: boolean;
18 social: boolean;
19}
20
21// Création d'un type qui est l'intersection de trois interfaces
22type FullDinosaurProfile = BasicDinosaur & PhysicalTraits & BehavioralTraits;
23
24// Maintenant l'objet doit implémenter toutes les propriétés des trois interfaces
25const velociraptor: FullDinosaurProfile = {
26 // De BasicDinosaur
27 id: 101,
28 name: "Blue",
29 species: "Velociraptor",
30 age: 4,
31
32 // De PhysicalTraits
33 height: 0.5,
34 length: 2,
35 weight: 15,
36
37 // De BehavioralTraits
38 aggressionLevel: 8,
39 territorial: true,
40 social: true
41};Applications pratiques des types intersection
Les types intersection sont particulièrement utiles pour construire des systèmes complexes au Jurassic Park :
1. Composer des comportements
1// Définition de différentes capacités
2interface Swimming {
3 swimmingSpeed: number; // km/h
4 maxDepth: number; // meters
5 underwaterEndurance: number; // minutes
6}
7
8interface Flying {
9 wingspan: number; // meters
10 flightSpeed: number; // km/h
11 maxAltitude: number; // meters
12}
13
14// Un dinosaure qui peut à la fois nager et voler (comme le Ptéranodon)
15type FlyingISwimmingDinosaur = BasicDinosaur & Flying & Swimming;
16
17const pteranodon: FlyingISwimmingDinosaur = {
18 id: 201,
19 name: "Pterri",
20 species: "Pteranodon",
21 age: 12,
22
23 // Capacités de vol
24 wingspan: 8,
25 flightSpeed: 90,
26 maxAltitude: 500,
27
28 // Capacités de nage
29 swimmingSpeed: 20,
30 maxDepth: 10,
31 underwaterEndurance: 3
32};2. Étendre des types existants
1// Interface de sécurité de base pour tous les enclos
2interface BasicSecuritySystem {
3 doubleFence: boolean;
4 videoMonitoring: boolean;
5 motionSensors: boolean;
6}
7
8// Mesures de sécurité supplémentaires pour les prédateurs
9interface PredatorSecurityMeasures {
10 electricVoltage: number; // volts
11 wallThickness: number; // cm
12 securityDome: boolean;
13}
14
15// Système de sécurité complet pour un enclos de prédateurs
16type PredatorEnclosure = BasicSecuritySystem & PredatorSecurityMeasures;
17
18// Utilisation de l'intersection pour ajouter des informations sur l'enclos
19type TRexEnclosure = PredatorEnclosure & {
20 area: number; // hectares
21 terrainLayout: string[];
22 realisticVegetation: boolean;
23};
24
25const rexEnclosure: TRexEnclosure = {
26 // BasicSecuritySystem
27 doubleFence: true,
28 videoMonitoring: true,
29 motionSensors: true,
30
31 // PredatorSecurityMeasures
32 electricVoltage: 10000,
33 wallThickness: 50,
34 securityDome: false,
35
36 // Spécifique à TRexEnclosure
37 area: 8,
38 terrainLayout: ["tall grass", "hills", "stream"],
39 realisticVegetation: true
40};3. Combiner des fonctionnalités dans des systèmes complexes
1// Interface du système d'observation
2interface ObservationSystem {
3 cameras: number;
4 resolution: string;
5 infraredTracking: boolean;
6
7 startRecording(): void;
8 stopRecording(): void;
9}
10
11// Interface du système d'alarme
12interface AlarmSystem {
13 soundSignal: boolean;
14 lightSignal: boolean;
15 smsNotification: boolean;
16
17 triggerAlarm(level: number): void;
18 disableAlarm(): void;
19}
20
21// Système de sécurité complexe combinant les deux fonctionnalités
22type ParkSecuritySystem = ObservationSystem & AlarmSystem;
23
24// Implémentation du système de sécurité
25class FullSecuritySystem implements ParkSecuritySystem {
26 // ObservationSystem
27 cameras: number = 24;
28 resolution: string = "4K";
29 infraredTracking: boolean = true;
30
31 startRecording(): void {
32 console.log("Starting recording from all cameras");
33 }
34
35 stopRecording(): void {
36 console.log("Stopping recording");
37 }
38
39 // AlarmSystem
40 soundSignal: boolean = true;
41 lightSignal: boolean = true;
42 smsNotification: boolean = true;
43
44 triggerAlarm(level: number): void {
45 console.log(`Activating level ${level} alarm!`);
46 if (this.soundSignal) console.log("Sirens activated");
47 if (this.lightSignal) console.log("Warning lights activated");
48 if (this.smsNotification) console.log("Sending SMS notifications to staff");
49 }
50
51 disableAlarm(): void {
52 console.log("Disabling all alarm systems");
53 }
54
55 // Méthodes supplémentaires propres au système complet
56 initiateSecurityProcedure(threatCode: string): void {
57 console.log(`Initiating security procedure: ${threatCode}`);
58 this.startRecording();
59 this.triggerAlarm(threatCode === "RED" ? 3 : 1);
60 }
61}
62
63// Utilisation du système
64const securitySystem = new FullSecuritySystem();
65securitySystem.initiateSecurityProcedure("RED");Combiner union et intersection - cas avancés
La véritable puissance de TypeScript se révèle quand nous combinons les types union et intersection en structures plus complexes, tout comme le Dr Wu combine l'ADN de différentes espèces pour créer des dinosaures entièrement nouveaux.
1// Types de dinosaures de base
2interface Dinosaur {
3 id: number;
4 name: string;
5 species: string;
6}
7
8interface LandDinosaur {
9 environment: "land";
10 runSpeed: number; // km/h
11}
12
13interface WaterDinosaur {
14 environment: "water";
15 swimmingSpeed: number; // km/h
16 maxDepth: number; // meters
17}
18
19interface AirDinosaur {
20 environment: "air";
21 flightSpeed: number; // km/h
22 maxAltitude: number; // meters
23}
24
25// Types pour différentes capacités
26interface HuntingAbilities {
27 biteForce: number; // PSI
28 huntingStrategy: "ambush" | "chase" | "pack";
29}
30
31interface HerbivoreDefensiveAbilities {
32 armor: boolean;
33 horns: boolean;
34 spikes: boolean;
35 defensiveStrength: number; // 1-10
36}
37
38// Type complexe utilisant union et intersection
39type ParkDinosaur = Dinosaur &
40 (
41 (LandDinosaur & HuntingAbilities) |
42 (LandDinosaur & HerbivoreDefensiveAbilities) |
43 (WaterDinosaur & HuntingAbilities) |
44 (AirDinosaur & HuntingAbilities)
45 );
46
47// Exemples d'utilisation
48const tyrannosaurus: ParkDinosaur = {
49 id: 1,
50 name: "Rex",
51 species: "Tyrannosaurus Rex",
52 environment: "land",
53 runSpeed: 30,
54 biteForce: 12800,
55 huntingStrategy: "chase"
56};
57
58const stegosaurus: ParkDinosaur = {
59 id: 2,
60 name: "Spike",
61 species: "Stegosaurus",
62 environment: "land",
63 runSpeed: 7,
64 armor: true,
65 horns: false,
66 spikes: true,
67 defensiveStrength: 8
68};
69
70// Fonction gérant des types complexes
71function analyzeDinosaur(dino: ParkDinosaur): void {
72 console.log(`Analyzing: ${dino.name} (${dino.species})`);
73
74 // Vérifier l'environnement
75 if (dino.environment === "land") {
76 console.log(`Running speed: ${dino.runSpeed} km/h`);
77
78 // Vérifier s'il s'agit d'un prédateur ou d'un herbivore
79 if ("biteForce" in dino) {
80 console.log(`Predator - bite force: ${dino.biteForce} PSI`);
81 console.log(`Hunting strategy: ${dino.huntingStrategy}`);
82 } else if ("defensiveStrength" in dino) {
83 console.log("Herbivore with defensive mechanisms:");
84 if (dino.armor) console.log("- Has armor");
85 if (dino.horns) console.log("- Has horns");
86 if (dino.spikes) console.log("- Has spikes");
87 console.log(`Defensive strength: ${dino.defensiveStrength}/10`);
88 }
89 } else if (dino.environment === "water") {
90 console.log(`Aquatic predator - swimming speed: ${dino.swimmingSpeed} km/h`);
91 console.log(`Maximum depth: ${dino.maxDepth} m`);
92 } else if (dino.environment === "air") {
93 console.log(`Aerial predator - flying speed: ${dino.flightSpeed} km/h`);
94 console.log(`Maximum flight altitude: ${dino.maxAltitude} m`);
95 }
96}
97
98analyzeDinosaur(tyrannosaurus);
99console.log("---");
100analyzeDinosaur(stegosaurus);Résumé
Les types union et intersection sont comme les techniques avancées de génie génétique du Dr Wu : ils nous donnent une puissance et une flexibilité extraordinaires pour concevoir des systèmes TypeScript complexes.
| Type | Opérateur | Analogie Jurassic Park | Utilisation | |
|---|---|---|---|---|
| Union | ` | ` | Enclos pouvant accueillir différents dinosaures (mais un seul à la fois) | Variables pouvant avoir différents types |
| Union discriminée | ` | ` avec propriété discriminante | Système d'identification des dinosaures pour différentes espèces | Gestion sûre de différentes variantes de types |
| Intersection | & | Combiner l'ADN de différents dinosaures | Combiner des interfaces/types en structures plus complexes |
Le Dr Wu résume la leçon d'aujourd'hui : « TypeScript, comme notre laboratoire de génétique, nous offre d'énormes possibilités. Grâce aux types union et intersection, nous pouvons créer des systèmes précisément adaptés à nos besoins, en combinant des éléments simples en structures complexes. Mais souviens-toi : un grand pouvoir implique de grandes responsabilités. Utilise ce savoir avec sagesse pour créer un parc sûr et fiable. »
Code de cette leçon : index.ts
1// Types union et intersection dans le Parc Jurassique
2console.log("Union et Intersection Types\n");
3
4// ===========================================
5// 1. UNION TYPES - type A ou B
6// ===========================================
7console.log("=== UNION TYPES ===");
8
9// Type union simple
10type DinosaurDiet = "carnivore" | "herbivore" | "omnivore";
11
12let trexDiet: DinosaurDiet = "carnivore";
13let brachiosaurDiet: DinosaurDiet = "herbivore";
14
15console.log("T-Rex diet:", trexDiet);
16console.log("Brachiosaurus diet:", brachiosaurDiet);
17
18// Union with different types
19type ID = number | string;
20
21let paddockId: ID = "P-01";
22let dinosaurId: ID = 123;
23
24console.log("Paddock ID:", paddockId);
25console.log("Dinosaur ID:", dinosaurId);
26
27// Union with objects
28interface Carnivore {
29 type: "carnivore";
30 huntingStyle: string;
31 preySize: string;
32}
33
34interface Herbivore {
35 type: "herbivore";
36 plantPreference: string;
37 grazingHours: number;
38}
39
40type Dinosaur = Carnivore | Herbivore;
41
42const trex: Carnivore = {
43 type: "carnivore",
44 huntingStyle: "ambush predator",
45 preySize: "large"
46};
47
48const triceratops: Herbivore = {
49 type: "herbivore",
50 plantPreference: "ferns and cycads",
51 grazingHours: 16
52};
53
54// Type guards pour les union types
55function describeDiet(dino: Dinosaur): string {
56 if (dino.type === "carnivore") {
57 return `Carnivore: ${dino.huntingStyle}, preys on ${dino.preySize} animals`;
58 } else {
59 return `Herbivore: prefers ${dino.plantPreference}, grazes ${dino.grazingHours}h/day`;
60 }
61}
62
63console.log("\nT-Rex:", describeDiet(trex));
64console.log("Triceratops:", describeDiet(triceratops));
65
66// ===========================================
67// 2. INTERSECTION TYPES - type A et B
68// ===========================================
69console.log("\n=== INTERSECTION TYPES ===");
70
71// Combining types
72interface HasName {
73 name: string;
74}
75
76interface HasWeight {
77 weight: number;
78}
79
80interface HasDiet {
81 diet: DinosaurDiet;
82}
83
84// Intersection - must have all properties
85type CompleteDinosaur = HasName & HasWeight & HasDiet;
86
87const velociraptor: CompleteDinosaur = {
88 name: "Blue",
89 weight: 350,
90 diet: "carnivore"
91};
92
93console.log("Complete dinosaur:", velociraptor);
94
95// Extending interfaces through intersection
96interface BasicInfo {
97 id: number;
98 species: string;
99}
100
101interface LocationInfo {
102 paddockId: string;
103 zone: string;
104}
105
106interface HealthInfo {
107 isHealthy: boolean;
108 lastCheckup: Date;
109}
110
111type FullDinosaurInfo = BasicInfo & LocationInfo & HealthInfo;
112
113const rexy: FullDinosaurInfo = {
114 id: 1,
115 species: "Tyrannosaurus Rex",
116 paddockId: "P-01",
117 zone: "Carnivore Zone",
118 isHealthy: true,
119 lastCheckup: new Date()
120};
121
122console.log("\nFull info:", rexy);
123
124// ===========================================
125// 3. Combining Union and Intersection
126// ===========================================
127console.log("\n=== UNION + INTERSECTION ===");
128
129interface Swimmer {
130 swimSpeed: number;
131}
132
133interface Runner {
134 runSpeed: number;
135}
136
137interface Flyer {
138 flySpeed: number;
139}
140
141// Dinosaur can swim OR fly, BUT must be able to run
142type ActiveDinosaur = Runner & (Swimmer | Flyer);
143
144const mosasaurus: ActiveDinosaur = {
145 runSpeed: 5, // slow on land
146 swimSpeed: 50 // rapide dans l'water
147};
148
149const pteranodon: ActiveDinosaur = {
150 runSpeed: 10,
151 flySpeed: 80
152};
153
154console.log("Mosasaurus:", mosasaurus);
155console.log("Pteranodon:", pteranodon);
156
157// ===========================================
158// 4. Applications pratiques
159// ===========================================
160console.log("\n=== APPLICATIONS PRATIQUES ===");
161
162// API Response type
163type SuccessResponse = {
164 status: "success";
165 data: any;
166};
167
168type ErrorResponse = {
169 status: "error";
170 message: string;
171 code: number;
172};
173
174type ApiResponse = SuccessResponse | ErrorResponse;
175
176function handleResponse(response: ApiResponse): void {
177 if (response.status === "success") {
178 console.log("Data received:", response.data);
179 } else {
180 console.log(`Error ${response.code}: ${response.message}`);
181 }
182}
183
184const success: ApiResponse = {
185 status: "success",
186 data: { dinosaurs: [trex, triceratops] }
187};
188
189const error: ApiResponse = {
190 status: "error",
191 message: "Paddock breach detected",
192 code: 503
193};
194
195handleResponse(success);
196handleResponse(error);
197
198// Event types
199interface BaseEvent {
200 timestamp: Date;
201 source: string;
202}
203
204interface FeedingEvent extends BaseEvent {
205 type: "feeding";
206 dinosaurId: number;
207 foodAmount: number;
208}
209
210interface EscapeEvent extends BaseEvent {
211 type: "escape";
212 dinosaurId: number;
213 paddockId: string;
214 severity: "low" | "high";
215}
216
217interface MaintenanceEvent extends BaseEvent {
218 type: "maintenance";
219 paddockId: string;
220 duration: number;
221}
222
223type ParkEvent = FeedingEvent | EscapeEvent | MaintenanceEvent;
224
225function logEvent(event: ParkEvent): void {
226 console.log(`[${event.timestamp.toISOString()}] ${event.source}`);
227
228 switch (event.type) {
229 case "feeding":
230 console.log(` Feeding dinosaur #${event.dinosaurId}: ${event.foodAmount}kg`);
231 break;
232 case "escape":
233 console.log(` ESCAPE from paddock ${event.paddockId}! Severity: ${event.severity}`);
234 break;
235 case "maintenance":
236 console.log(` Maintenance on paddock ${event.paddockId}: ${event.duration}h`);
237 break;
238 }
239}
240
241const feedingEvent: FeedingEvent = {
242 type: "feeding",
243 timestamp: new Date(),
244 source: "Automated System",
245 dinosaurId: 1,
246 foodAmount: 400
247};
248
249const escapeEvent: EscapeEvent = {
250 type: "escape",
251 timestamp: new Date(),
252 source: "Security Alert",
253 dinosaurId: 1,
254 paddockId: "P-01",
255 severity: "high"
256};
257
258console.log("\nEvent log:");
259logEvent(feedingEvent);
260logEvent(escapeEvent);
261
262// ===========================================
263// 5. Résumé
264// ===========================================
265console.log("\n=== RÉSUMÉ ===");
266console.log("Union Types (|) - value can be one of the types");
267console.log("Intersection Types (&) - value must satisfy all types");
268console.log("Type Guards - vérification du type en runtime");
269console.log("Discriminated Unions - distinguishing by field");
270
271console.log("\nUnion et Intersection Types maîtrisés !");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 signifie le type string | number en TypeScript ?
2. Que fait le type A & B (type intersection) ?
Tâches pratiques dans le jeu
- Éditeur de code
Crée un type union DietType = 'herbivore' | 'carnivore' | 'omnivore' et un type intersection AquaticDino = Dinosaur & SwimmingAbility.
- Tri horizontal
Ordonne les éléments d’un alias de type :
- Clique dans l'ordre
Ordonne les éléments d’une définition de type union :
- Tri vertical
Ordonne les étapes de réduction (narrowing) d’un type union en TypeScript :