Cours JavaScript et TypeScript · Module 6: Bases de TypeScript

Les types union et intersection

10 min de lecture
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 | number

Applications 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 filtres

Les 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.

TypeOpérateurAnalogie Jurassic ParkUtilisation
Union``Enclos pouvant accueillir différents dinosaures (mais un seul à la fois)Variables pouvant avoir différents types
Union discriminée`` avec propriété discriminanteSystème d'identification des dinosaures pour différentes espècesGestion sûre de différentes variantes de types
Intersection&Combiner l'ADN de différents dinosauresCombiner 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. 1. Que signifie le type string | number en TypeScript ?

  2. 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 :

Articles utiles