Cours JavaScript et TypeScript · Module 7: TypeScript avancé
Projet final : système de types avancé pour Jurassic Park
Dans cette leçon4
« Le moment du bilan est venu », annonce le Dr Henry Wu, généticien en chef d'InGen, debout devant l'immense écran holographique du laboratoire. « Au cours des dernières semaines, tu as maîtrisé des mécanismes avancés de TypeScript - des modificateurs d'accès aux types conditionnels et aux types mappés, en passant par les génériques. Maintenant, tu vas construire un système complet de gestion de Jurassic Park qui combine tous ces éléments en un tout cohérent. »
Dans ce projet, tu vas créer un système de types avancé pour Jurassic Park. Tu utiliseras des classes abstraites, des interfaces, des génériques, des types conditionnels, des types mappés, des types utilitaires et des décorateurs - tout ce que tu as appris dans ce module. Le système gérera l'enregistrement des dinosaures, la gestion des enclos, la surveillance de la santé et la génération de rapports de sécurité.
Objectif du projet
Tu vas construire un système complet et entièrement typé JurassicParkManager qui :
- Utilise des classes abstraites comme base pour différentes catégories de dinosaures
- Implémente des interfaces définissant les contrats des sous-systèmes du parc
- Utilise des génériques pour créer des structures de données flexibles
- Applique des types conditionnels pour déterminer dynamiquement les comportements
- Met à profit des types mappés pour la transformation des données
- Tire parti des types utilitaires pour une modélisation précise
Exigences fonctionnelles
1. Hiérarchie de classes de dinosaures avec classes abstraites et interfaces
Définis une classe de base abstraite Dinosaur avec des propriétés protégées et des méthodes abstraites. Crée ensuite des interfaces décrivant différentes capacités des dinosaures et implémente-les dans des classes dérivées.
1// Ability interfaces
2interface IHerbivore {
3 graze(area: string): string;
4 preferredPlants: string[];
5}
6
7interface ICarnivore {
8 hunt(target: string): string;
9 attackPower: number;
10}
11
12interface IFlying {
13 fly(altitude: number): string;
14 maxAltitude: number;
15}
16
17interface ISwimming {
18 swim(depth: number): string;
19 maxDepth: number;
20}
21
22// Abstract class with access modifiers
23abstract class Dinosaur {
24 private static nextId: number = 1;
25
26 protected readonly id: number;
27 public readonly species: string;
28 protected health: number;
29 private enclosureId: string | null = null;
30
31 constructor(species: string, health: number = 100) {
32 this.id = Dinosaur.nextId++;
33 this.species = species;
34 this.health = health;
35 }
36
37 // Abstract method - each subtype defines its own danger level
38 abstract get dangerLevel(): 'low' | 'medium' | 'high' | 'critical';
39
40 // Abstract method - behavior description
41 abstract describeBehavior(): string;
42
43 // Static factory method
44 static getNextId(): number {
45 return Dinosaur.nextId;
46 }
47
48 // Public API
49 public getStatus(): string {
50 return `[${this.id}] ${this.species} - Health: ${this.health}%`;
51 }
52
53 public assignEnclosure(enclosureId: string): void {
54 this.enclosureId = enclosureId;
55 }
56
57 protected heal(amount: number): void {
58 this.health = Math.min(100, this.health + amount);
59 }
60}
61
62// Derived class implementing interfaces
63class TyrannosaurusRex extends Dinosaur implements ICarnivore {
64 public attackPower: number = 95;
65
66 constructor() {
67 super('Tyrannosaurus Rex', 100);
68 }
69
70 get dangerLevel(): 'critical' {
71 return 'critical';
72 }
73
74 describeBehavior(): string {
75 return 'Apex predator - requires maximum security';
76 }
77
78 hunt(target: string): string {
79 return `T-Rex hunts ${target} with attack power ${this.attackPower}`;
80 }
81}
82
83class Pteranodon extends Dinosaur implements ICarnivore, IFlying {
84 public attackPower: number = 40;
85 public maxAltitude: number = 500;
86
87 constructor() {
88 super('Pteranodon', 100);
89 }
90
91 get dangerLevel(): 'high' {
92 return 'high';
93 }
94
95 describeBehavior(): string {
96 return 'Flying predator - requires a roofed enclosure';
97 }
98
99 hunt(target: string): string {
100 return `Pteranodon dives at ${target} from the air`;
101 }
102
103 fly(altitude: number): string {
104 if (altitude > this.maxAltitude) {
105 return 'Altitude exceeds maximum ceiling!';
106 }
107 return `Pteranodon flies at altitude ${altitude}m`;
108 }
109}2. Structures de données génériques avec contraintes
Crée des classes et des fonctions génériques avec des contraintes appropriées pour garantir la sécurité des types dans tout le système.
1// Generic registry class with a constraint
2class ParkRegistry<T extends Dinosaur> {
3 private entries: Map<number, T> = new Map();
4
5 add(item: T): void {
6 this.entries.set(item['id'], item);
7 }
8
9 get(id: number): T | undefined {
10 return this.entries.get(id);
11 }
12
13 getAll(): T[] {
14 return Array.from(this.entries.values());
15 }
16
17 // Generic method with an additional constraint
18 filter<K extends keyof T>(key: K, value: T[K]): T[] {
19 return this.getAll().filter(item => item[key] === value);
20 }
21
22 // Method with multiple generic types
23 map<U>(transform: (item: T) => U): U[] {
24 return this.getAll().map(transform);
25 }
26}
27
28// Generic function with multiple constraints
29function transferDinosaur<
30 T extends Dinosaur,
31 S extends ParkRegistry<T>,
32 D extends ParkRegistry<T>
33>(source: S, destination: D, dinoId: number): boolean {
34 const dino = source.get(dinoId);
35 if (!dino) return false;
36
37 destination.add(dino);
38 return true;
39}
40
41// Generic interface for monitoring systems
42interface IMonitoringSystem<T extends Dinosaur> {
43 monitor(subject: T): MonitoringReport<T>;
44 getAlerts(): Alert<T>[];
45}
46
47// Generic report type
48interface MonitoringReport<T extends Dinosaur> {
49 subject: T;
50 timestamp: Date;
51 status: 'normall' | 'warning' | 'danger';
52 details: string;
53}
54
55interface Alert<T extends Dinosaur> {
56 dinosaur: T;
57 level: 'info' | 'warning' | 'critical';
58 message: string;
59}3. Types conditionnels et mappés pour une modélisation dynamique
Utilise des types conditionnels et des types mappés pour adapter automatiquement le comportement du système selon le type de dinosaure et le niveau de danger.
1// Conditional type - determines enclosure requirements based on danger level
2type DangerLevel = 'low' | 'medium' | 'high' | 'critical';
3
4type EnclosureRequirements<D extends DangerLevel> =
5 D extends 'critical' ? {
6 fenceVoltage: number;
7 guardCount: number;
8 cameraCount: number;
9 backupPower: boolean;
10 emergencyProtocol: string;
11 } :
12 D extends 'high' ? {
13 fenceVoltage: number;
14 guardCount: number;
15 cameraCount: number;
16 backupPower: boolean;
17 } :
18 D extends 'medium' ? {
19 fenceVoltage: number;
20 cameraCount: number;
21 } :
22 {
23 fenceType: string;
24 };
25
26// Using the conditional type
27const trexEnclosure: EnclosureRequirements<'critical'> = {
28 fenceVoltage: 10000,
29 guardCount: 4,
30 cameraCount: 12,
31 backupPower: true,
32 emergencyProtocol: 'Sector evacuation and dispatch of response team'
33};
34
35// Mapped types - generating readonly and optional versions
36type DinosaurData = {
37 species: string;
38 health: number;
39 location: string;
40 lastFed: Date;
41 dangerLevel: DangerLevel;
42};
43
44// Mapped type: display version (readonly)
45type ReadonlyDinosaurData = {
46 readonly [K in keyof DinosaurData]: DinosaurData[K];
47};
48
49// Mapped type: update version (everything optional)
50type DinosaurUpdate = {
51 [K in keyof DinosaurData]?: DinosaurData[K];
52};
53
54// Mapped type: extract only string fields
55type StringFieldsOnly<T> = {
56 [K in keyof T as T[K] extends string ? K : never]: T[K];
57};
58
59type DinosaurStringFields = StringFieldsOnly<DinosaurData>;
60// Result: { species: string; location: string; }
61
62// Advanced mapped type with conditional transformation
63type DinosaurDisplay<T> = {
64 [K in keyof T]: T[K] extends Date
65 ? string // Convert dates to strings
66 : T[K] extends number
67 ? `${number}%` // Numbers with percent
68 : T[K]; // Rest unchanged
69};
70
71// Conditional type with infer
72type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
73
74type DinosaurFactory = (species: string) => Dinosaur;
75type CreatedDino = ExtractReturnType<DinosaurFactory>; // Dinosaur
76
77// Distributive conditional type
78type ExcludeDangerLevels<T, U> = T extends U ? never : T;
79type SafeLevels = ExcludeDangerLevels<DangerLevel, 'high' | 'critical'>;
80// Result: 'low' | 'medium'4. Types utilitaires et motifs de type avancés
Applique des types utilitaires intégrés et crée les tiens pour modéliser précisément les opérations sur les données des dinosaures.
1// Built-in utility types
2type NewDinosaur = Omit<DinosaurData, 'lastFed'> & {
3 registrationDate: Date;
4};
5
6type DinosaurPreview = Pick<DinosaurData, 'species' | 'dangerLevel'>;
7
8type DinosaurRecord = Record<string, DinosaurData>;
9
10// Requiring fields that were previously optional
11type RequiredDinosaurUpdate = Required<DinosaurUpdate>;
12
13// Custom utility types
14type DeepReadonly<T> = {
15 readonly [K in keyof T]: T[K] extends object
16 ? T[K] extends Function
17 ? T[K]
18 : DeepReadonly<T[K]>
19 : T[K];
20};
21
22type DeepPartial<T> = {
23 [K in keyof T]?: T[K] extends object
24 ? T[K] extends Function
25 ? T[K]
26 : DeepPartial<T[K]>
27 : T[K];
28};
29
30// Type that excludes null and undefined from every field
31type NonNullableFields<T> = {
32 [K in keyof T]: NonNullable<T[K]>;
33};
34
35// Enclosure configuration system with full typing
36interface EnclosureConfig {
37 dimensions: {
38 width: number;
39 height: number;
40 depth: number;
41 };
42 security: {
43 fenceType: 'electric' | 'reinforced' | 'laser';
44 voltage?: number;
45 guardSchedule: string[];
46 };
47 environment: {
48 temperature: number;
49 humidity: number;
50 terrain: 'forest' | 'plains' | 'water' | 'mixed';
51 };
52}
53
54// Deep partial allows updating any arbitrarily nested field
55type EnclosureUpdate = DeepPartial<EnclosureConfig>;
56
57const partialUpdate: EnclosureUpdate = {
58 security: {
59 voltage: 15000 // Update only voltage
60 }
61};
62
63// Deep readonly for configuration that should not be changed
64type FrozenConfig = DeepReadonly<EnclosureConfig>;Conseils d'implémentation
Commence par définir les interfaces et les types de base - c'est le fondement de tout le système. Crée ensuite la classe abstraite Dinosaur et plusieurs classes dérivées implémentant différentes interfaces de capacités. Construis ensuite un ParkRegistry générique et un système de surveillance. Enfin, ajoute des types conditionnels et mappés qui adaptent dynamiquement le comportement du système.
Souviens-toi de la structure de fichiers suivante :
1// 1. Base types and interfaces
2// 2. Abstract Dinosaur class
3// 3. Derived classes (TRex, Velociraptor, Triceratops, Pteranodon...)
4// 4. Generic ParkRegistry<T>
5// 5. Monitoring system with conditional types
6// 6. Utility types and transformations
7// 7. Main JurassicParkManager class combining everythingExemple d'initialisation du système :
1// Create registry
2const carnivoreRegistry = new ParkRegistry<TyrannosaurusRex>();
3const herbivoreRegistry = new ParkRegistry<Triceratops>();
4
5// Add dinosaurs
6const rex = new TyrannosaurusRex();
7carnivoreRegistry.add(rex);
8
9// Enclosure configuration with conditional types
10const criticalEnclosure: EnclosureRequirements<'critical'> = {
11 fenceVoltage: 10000,
12 guardCount: 4,
13 cameraCount: 12,
14 backupPower: true,
15 emergencyProtocol: 'Code Red - full evacuation'
16};
17
18// Generate report
19const reports = carnivoreRegistry.map(dino => ({
20 species: dino.species,
21 status: dino.getStatus(),
22 danger: dino.dangerLevel
23}));
24
25console.log('=== Jurassic Park Report ===');
26reports.forEach(r => console.log(`${r.species}: ${r.status} [Danger: ${r.danger}]`));Bonne chance !
« Souviens-toi », dit le Dr Wu en ajustant ses lunettes, « le système de types de TypeScript est ton ADN - le fondement sur lequel tu bâtis tout l'écosystème. Mieux tu conçois les types, plus ton Parc sera stable. Les génériques apportent la flexibilité, les types conditionnels - l'intelligence, et les types mappés - la capacité de transformation. Combine-les tous et crée un système qui résistera à l'épreuve du temps... et à l'épreuve des dinosaures en cavale. »
Il est temps d'appliquer tout ce que tu as appris. Ouvre l'éditeur et construis ton système de types avancé !
Code de cette leçon : index.ts
1// Projet final : Système de types avancé pour le Parc Jurassique
2// Combine : génériques, types conditionnels, types mappés, types utilitaires
3
4// === 1. INTERFACES AVEC GÉNÉRIQUES ===
5// TODO : Crée une interface générique Repository<T>
6interface Repository<T extends { id: string }> {
7 items: T[];
8 add(item: T): void;
9 findById(id: string): T | undefined;
10 // TODO : Ajoute les méthodes : remove, update, filter
11}
12
13// === 2. CLASSE ABSTRAITE AVEC GÉNÉRIQUES ===
14// TODO : Crée une classe abstraite Dinosaur avec des modificateurs d'accès
15abstract class Dinosaur {
16 protected readonly id: string;
17 protected name: string;
18
19 constructor(id: string, name: string) {
20 this.id = id;
21 this.name = name;
22 }
23
24 // TODO : Ajoute des méthodes abstraites : getDangerLevel(), getSound()
25 abstract getDangerLevel(): number;
26 abstract getSound(): string;
27}
28
29// TODO : Crée les classes Carnivore et Herbivore étendant Dinosaur
30
31// === 3. TYPES CONDITIONNELS ===
32// TODO : Crée un type conditionnel DinoResponse<T>
33// Si T extends "carnivore" -> retourne CarnivoreData
34// Si T extends "herbivore" -> retourne HerbivoreData
35type DinoCategory = "carnivore" | "herbivore";
36
37interface CarnivoreData {
38 attackPower: number;
39 huntingStyle: string;
40}
41
42interface HerbivoreData {
43 grazingArea: number;
44 preferredPlants: string[];
45}
46
47// TODO : Définis le type conditionnel DinoResponse<T extends DinoCategory>
48
49// === 4. TYPES MAPPÉS ===
50// TODO : Crée un type ReadonlyDino<T> (deep readonly)
51// TODO : Crée un type Optional<T> (tous les champs optionnels)
52// TODO : Crée un type DinoFormFields<T> (valeurs converties en string)
53
54// === 5. PARK MANAGER ===
55// TODO : Crée une classe JurassicParkManager utilisant des génériques
56// avec des méthodes pour l'enregistrement, la recherche et le reporting
57
58console.log("Système de types avancé démarré !");Tu as repéré une erreur dans cette leçon ?