Cours JavaScript et TypeScript · Module 6: Bases de TypeScript

Définitions d'interfaces

12 min de lecture
Dans cette leçon11

Bienvenue de nouveau au Jurassic Park ! Aujourd'hui, sous l'œil attentif du Dr Henry Wu, nous allons explorer un concept fondamental de TypeScript, tout aussi crucial pour l'organisation du code que les protocoles de sécurité le sont pour le bon fonctionnement du parc : les interfaces.

Que sont les interfaces en TypeScript ?

Les interfaces en TypeScript définissent des contrats dans le code et décrivent la structure des objets. Elles sont comme des protocoles détaillés qui précisent ce qu'un objet donné doit contenir. Dans le contexte du Jurassic Park, on peut comparer les interfaces aux normes de sécurité et aux protocoles de manipulation des différentes espèces de dinosaures : chaque espèce a ses propres exigences et traits spécifiques.

Syntaxe de base d'une interface

On définit une interface à l'aide du mot-clé interface :

1interface Dinosaur {
2  id: number;
3  name: string;
4  species: string;
5  age: number;
6  weight: number;
7}
8
9// On crée un objet qui doit satisfaire les exigences de l'interface
10const rex: Dinosaur = {
11  id: 1,
12  name: "Rex",
13  species: "Tyrannosaurus",
14  age: 7,
15  weight: 8000
16};
17
18// Le code suivant provoquerait une erreur car des propriétés obligatoires sont manquantes
19// const raptor: Dinosaur = {
20//   id: 2,
21//   name: "Blue"
22// };

Les propriétés optionnelles

Certaines propriétés d'une interface peuvent être optionnelles, signalées par l'ajout d'un point d'interrogation (?). C'est comme marquer des procédures de manipulation optionnelles pour des dinosaures particuliers.

1interface DinosaurSpecimen {
2  id: number;
3  name: string;
4  species: string;
5  rfidTag?: string;         // Puce RFID optionnelle
6  lastExamination?: Date;   // Date du dernier examen optionnelle
7  notes?: string;           // Notes de recherche optionnelles
8}
9
10// Objet satisfaisant l'interface, mais sans toutes les propriétés optionnelles
11const specimen1: DinosaurSpecimen = {
12  id: 101,
13  name: "Specimen A",
14  species: "Velociraptor"
15};
16
17// Objet avec certaines propriétés optionnelles
18const specimen2: DinosaurSpecimen = {
19  id: 102,
20  name: "Specimen B",
21  species: "Dilophosaurus",
22  rfidTag: "DILO-102",
23  notes: "Shows aggressive behavior in the presence of new objects"
24};

Lecture seule (readonly)

Certaines propriétés ne devraient être modifiées qu'au moment de la création de l'objet. En TypeScript, on peut y parvenir grâce au modificateur readonly. C'est comme des procédures de sécurité qui, une fois établies, ne peuvent pas être modifiées pendant les opérations normales.

1interface GeneticCode {
2  readonly id: string;
3  readonly sequence: string;
4  readonly donorSpecies: string;
5  modifications: string[];
6  isolationDate: Date;
7}
8
9// Création du code génétique
10const trexCode: GeneticCode = {
11  id: "GEN-TREX-001",
12  sequence: "ACGTTGCAAGCTA...",
13  donorSpecies: "West African frog",
14  modifications: ["Gap DNA filling", "Aggression gene correction"],
15  isolationDate: new Date("2022-05-15")
16};
17
18// On peut ajouter de nouvelles modifications
19trexCode.modifications.push("Immune resistance enhancement");
20
21// Mais on ne peut pas modifier les valeurs des champs readonly
22// trexCode.id = "GEN-TREX-002"; // Erreur : Cannot assign to 'id' because it is a read-only property
23// trexCode.sequence = "GCTA..."; // Erreur : Cannot assign to 'sequence' because it is a read-only property

Interfaces de fonction

Les interfaces peuvent aussi définir des types de fonction, ce qui est utile pour décrire des callbacks ou des fonctions passées en paramètre. Dans le contexte du parc, c'est comme définir des procédures standard pour gérer différentes situations.

1// Interface de la fonction de contrôle de sécurité
2interface SecurityCheck {
3  (threatLevel: number, parkZone: string): boolean;
4}
5
6// Implémentation d'une fonction conforme à l'interface
7const checkAccess: SecurityCheck = function(level, zone) {
8  // Logique de vérification d'accès
9  if (zone === "T-Rex Enclosure" && level < 5) {
10    return false; // Refuser l'accès à la zone du T-Rex au personnel en dessous du niveau 5
11  }
12
13  if (zone.includes("Laboratory") && level < 4) {
14    return false; // Refuser l'accès aux laboratoires au personnel en dessous du niveau 4
15  }
16
17  return true; // Accès autorisé dans les autres cas
18};
19
20// Utilisation de la fonction
21console.log(checkAccess(3, "T-Rex Enclosure")); // false
22console.log(checkAccess(5, "T-Rex Enclosure")); // true
23console.log(checkAccess(3, "Public area")); // true

Les types indexables

Les interfaces peuvent définir des types indexables, utiles quand nous avons des objets avec un nombre dynamique de propriétés du même type. Dans le contexte du parc, cela pourrait être une base de données de dinosaures indexée par leurs identifiants.

1// Interface pour une base de données de dinosaures
2interface DinosaursDatabase {
3  [id: string]: {
4    name: string;
5    species: string;
6    age: number;
7    health: "good" | "needs attention" | "critical";
8  };
9}
10
11// Création de la base de données
12const database: DinosaursDatabase = {
13  "TREX-001": {
14    name: "Rex",
15    species: "Tyrannosaurus",
16    age: 7,
17    health: "good"
18  },
19  "VELOC-001": {
20    name: "Blue",
21    species: "Velociraptor",
22    age: 4,
23    health: "good"
24  },
25  "TRIC-003": {
26    name: "Trike",
27    species: "Triceratops",
28    age: 10,
29    health: "needs attention"
30  }
31};
32
33// Accès aux données
34console.log(database["VELOC-001"].name); // "Blue"
35
36// Ajout d'une nouvelle entrée
37database["STEGO-002"] = {
38  name: "Spike",
39  species: "Stegosaurus",
40  age: 6,
41  health: "good"
42};

Interfaces de classe

Les interfaces peuvent aussi définir des contrats que les classes doivent implémenter. C'est comme des normes opérationnelles que chaque service du parc doit respecter.

1// Interface pour les systèmes de sécurité
2interface SecuritySystem {
3  name: string;
4  isActive: boolean;
5  securityLevel: number;
6
7  activate(): void;
8  deactivate(): void;
9  checkStatus(): string;
10}
11
12// Classe implémentant l'interface
13class ElectronicFenceSystem implements SecuritySystem {
14  name: string;
15  isActive: boolean;
16  securityLevel: number;
17  voltage: number; // Propriété supplémentaire propre à cette classe
18
19  constructor(name: string, securityLevel: number, voltage: number) {
20    this.name = name;
21    this.isActive = false; // Inactif au départ
22    this.securityLevel = securityLevel;
23    this.voltage = voltage;
24  }
25
26  activate(): void {
27    this.isActive = true;
28    console.log(`System ${this.name} activated. Voltage: ${this.voltage}V`);
29  }
30
31  deactivate(): void {
32    this.isActive = false;
33    console.log(`System ${this.name} deactivated.`);
34  }
35
36  checkStatus(): string {
37    return this.isActive
38      ? `${this.name}: ACTIVE (Level ${this.securityLevel})`
39      : `${this.name}: INACTIVE`;
40  }
41
42  // Méthode propre à cette classe
43  increaseVoltage(by: number): void {
44    this.voltage += by;
45    console.log(`Voltage increased to ${this.voltage}V`);
46  }
47}
48
49// Utilisation de la classe
50const trexFence = new ElectronicFenceSystem("T-Rex Fence", 5, 10000);
51trexFence.activate();
52console.log(trexFence.checkStatus());
53trexFence.increaseVoltage(2000);

Étendre des interfaces

Les interfaces peuvent étendre d'autres interfaces, un peu comme en biologie les espèces partagent des traits communs mais ont aussi leurs propres propriétés uniques.

1// Interface de base pour tous les animaux du parc
2interface Animal {
3  id: number;
4  species: string;
5  age: number;
6  weight: number;
7}
8
9// Interface pour les dinosaures étendant l'interface Animal
10interface Dinosaur extends Animal {
11  era: string; // ex. "Cretaceous", "Jurassic"
12  isPredator: boolean;
13  mainDiet: string[];
14}
15
16// Interface pour les dinosaures prédateurs étendant l'interface Dinosaur
17interface PredatoryDinosaur extends Dinosaur {
18  biteForce: number;  // en PSI
19  runSpeed: number;   // en km/h
20  huntingBehavior: string[];
21}
22
23// Création d'un objet conforme à l'interface PredatoryDinosaur
24const velociraptor: PredatoryDinosaur = {
25  id: 101,
26  species: "Velociraptor",
27  age: 4,
28  weight: 70,
29  era: "Cretaceous",
30  isPredator: true,
31  mainDiet: ["small mammals", "lizards", "other small dinosaurs"],
32  biteForce: 1000,
33  runSpeed: 60,
34  huntingBehavior: ["pack hunting", "ambush", "chase"]
35};

La fusion de déclarations pour les interfaces

En TypeScript, il est possible de définir la même interface plusieurs fois : les déclarations seront fusionnées. C'est utile quand nous voulons ajouter de nouvelles propriétés à une interface existante.

1// Définition initiale du protocole de sécurité
2interface SecurityProtocol {
3  name: string;
4  threatLevel: number;
5  evacuationProcedure: string;
6}
7
8// Extension du protocole avec des propriétés supplémentaires
9interface SecurityProtocol {
10  responsibleStaff: string[];
11  responseTime: number; // en minutes
12}
13
14// L'objet doit contenir toutes les propriétés des deux déclarations
15const alphaProtocol: SecurityProtocol = {
16  name: "Protocol Alpha",
17  threatLevel: 3,
18  evacuationProcedure: "Evacuation towards the main control center",
19  responsibleStaff: ["Dr. Wu", "Owen Grady", "Claire Dearing"],
20  responseTime: 15
21};

Interfaces et types

TypeScript propose aussi le mot-clé type pour définir des types. Bien que dans de nombreux cas les interfaces et les types soient interchangeables, il existe des différences entre eux :

1// Définition à l'aide d'une interface
2interface EvacuationPlan {
3  route: string[];
4  estimatedTime: number;
5  assemblyPoints: string[];
6}
7
8// Définition équivalente à l'aide d'un type
9type EvacuationPlanType = {
10  route: string[];
11  estimatedTime: number;
12  assemblyPoints: string[];
13};
14
15// Les deux fonctionnent de manière similaire pour un usage de base
16const planA: EvacuationPlan = {
17  route: ["Operations center", "Main Street", "Evacuation hall"],
18  estimatedTime: 25,
19  assemblyPoints: ["Operations center", "Evacuation hall"]
20};
21
22const planB: EvacuationPlanType = {
23  route: ["Gallimimus Enclosure", "Innovation Center", "Evacuation hall"],
24  estimatedTime: 30,
25  assemblyPoints: ["Innovation Center", "Evacuation hall"]
26};

Principales différences entre interface et type :

  1. Extensibilité :

    • Les interfaces peuvent être étendues par plusieurs déclarations portant le même nom
    • Les types ne peuvent pas être « rouverts » : une fois déclaré, un type ne peut plus être étendu
  2. Types complexes :

    • Les types peuvent représenter directement des types primitifs, des unions, des intersections
    • Les interfaces sont davantage axées sur la définition de la forme des objets
1// Possible uniquement avec les types
2type DinosaurID = string | number;
3type EnclosureCoordinates = [number, number, string]; // Tuple
4type DinosaurStatus = "active" | "resting" | "sleeping"; // Union de littéraux
5
6// Plus naturel avec les interfaces
7interface Procedure {
8  name: string;
9  steps: string[];
10  executionTime: number;
11  responsibleStaff: string[];
12}

Application pratique des interfaces

Voyons comment nous pouvons utiliser les interfaces pour modéliser le système de gestion du Jurassic Park :

1// Interfaces de base
2interface ParkPerson {
3  id: string;
4  firstName: string;
5  lastName: string;
6  accessLevel: 1 | 2 | 3 | 4 | 5;
7  hireDate: Date;
8}
9
10interface Location {
11  id: string;
12  name: string;
13  type: "enclosure" | "laboratory" | "operations center" | "public zone";
14  coordinates: [number, number]; // [latitude, longitude]
15  requiredAccessLevel: 1 | 2 | 3 | 4 | 5;
16}
17
18interface DinosaurSpecies {
19  name: string;
20  geologicalEra: string;
21  type: "herbivore" | "predator" | "omnivore";
22  averageWeight: number;
23  averageAge: number;
24  dangerLevel: 1 | 2 | 3 | 4 | 5;
25  dietRequirements: string[];
26  habitatRequirements: string[];
27}
28
29interface DinosaurSpecimen {
30  id: string;
31  species: string;
32  firstName?: string;
33  age: number;
34  weight: number;
35  gender: "male" | "female";
36  location: string; // ID de la localisation
37  healthStatus: "excellent" | "good" | "needs attention" | "sick" | "critical";
38  lastExamination: Date;
39  rfidTag: string;
40  notes?: string;
41}
42
43// Interfaces pour les systèmes de gestion
44interface SpecimenTrackingSystem {
45  getPosition(specimenId: string): [number, number];
46  trackMovement(specimenId: string, trackingTime: number): [number, number][];
47  checkIfInZone(specimenId: string, zoneId: string): boolean;
48  alertWhenOutsideZone(specimenId: string, callback: (specimen: DinosaurSpecimen) => void): void;
49}
50
51interface EmployeeManagementSystem {
52  addEmployee(employee: ParkPerson): void;
53  removeEmployee(id: string): boolean;
54  grantAccess(employeeId: string, level: 1 | 2 | 3 | 4 | 5): boolean;
55  checkPresenceInZone(employeeId: string, zoneId: string): boolean;
56}
57
58// Exemple d'implémentation
59class ParkManagementSystem {
60  private employees: ParkPerson[] = [];
61  private locations: Location[] = [];
62  private species: DinosaurSpecies[] = [];
63  private specimens: DinosaurSpecimen[] = [];
64
65  // Méthodes pour le personnel
66  addEmployee(employee: ParkPerson): void {
67    this.employees.push(employee);
68    console.log(`Added employee: ${employee.firstName} ${employee.lastName}`);
69  }
70
71  // Méthodes pour les localisations
72  addLocation(location: Location): void {
73    this.locations.push(location);
74    console.log(`Added location: ${location.name}`);
75  }
76
77  // Méthodes pour les espèces
78  addSpecies(species: DinosaurSpecies): void {
79    this.species.push(species);
80    console.log(`Added species: ${species.name}`);
81  }
82
83  // Méthodes pour les spécimens
84  addSpecimen(specimen: DinosaurSpecimen): void {
85    this.specimens.push(specimen);
86    console.log(`Added specimen: ${specimen.id} (${specimen.species})`);
87  }
88
89  // Méthodes de gestion
90  checkAccess(employeeId: string, locationId: string): boolean {
91    const employee = this.employees.find(p => p.id === employeeId);
92    const location = this.locations.find(l => l.id === locationId);
93
94    if (!employee || !location) return false;
95
96    return employee.accessLevel >= location.requiredAccessLevel;
97  }
98
99  findSpecimensBySpecies(speciesName: string): DinosaurSpecimen[] {
100    return this.specimens.filter(o => o.species === speciesName);
101  }
102
103  findSpecimensInZone(zoneId: string): DinosaurSpecimen[] {
104    return this.specimens.filter(o => o.location === zoneId);
105  }
106
107  generateParkStatusReport(): {
108    employeeCount: number;
109    locationCount: number;
110    speciesCount: number;
111    specimenCount: number;
112    specimenHealthStatus: {
113      excellent: number;
114      good: number;
115      needsAttention: number;
116      sick: number;
117      critical: number;
118    }
119  } {
120    // Analyse de l'état de santé de tous les spécimens
121    const healthStatus = {
122      excellent: 0,
123      good: 0,
124      needsAttention: 0,
125      sick: 0,
126      critical: 0
127    };
128
129    this.specimens.forEach(specimen => {
130      if (specimen.healthStatus === "excellent") healthStatus.excellent++;
131      else if (specimen.healthStatus === "good") healthStatus.good++;
132      else if (specimen.healthStatus === "needs attention") healthStatus.needsAttention++;
133      else if (specimen.healthStatus === "sick") healthStatus.sick++;
134      else if (specimen.healthStatus === "critical") healthStatus.critical++;
135    });
136
137    return {
138      employeeCount: this.employees.length,
139      locationCount: this.locations.length,
140      speciesCount: this.species.length,
141      specimenCount: this.specimens.length,
142      specimenHealthStatus: healthStatus
143    };
144  }
145}
146
147// Exemple d'utilisation
148const jurassicPark = new ParkManagementSystem();
149
150// Ajout d'une localisation
151jurassicPark.addLocation({
152  id: "LOC-001",
153  name: "T-Rex Enclosure",
154  type: "enclosure",
155  coordinates: [23.45, -67.89],
156  requiredAccessLevel: 4
157});
158
159// Ajout d'une espèce
160jurassicPark.addSpecies({
161  name: "Tyrannosaurus Rex",
162  geologicalEra: "Cretaceous",
163  type: "predator",
164  averageWeight: 7000,
165  averageAge: 30,
166  dangerLevel: 5,
167  dietRequirements: ["large amounts of meat", "calcium supplementation"],
168  habitatRequirements: ["large area", "natural vegetation", "water access"]
169});
170
171// Ajout d'un employé
172jurassicPark.addEmployee({
173  id: "EMP-001",
174  firstName: "Owen",
175  lastName: "Grady",
176  accessLevel: 5,
177  hireDate: new Date("2010-06-15")
178});
179
180// Ajout d'un spécimen
181jurassicPark.addSpecimen({
182  id: "DINO-001",
183  species: "Tyrannosaurus Rex",
184  firstName: "Rexy",
185  age: 7,
186  weight: 7500,
187  gender: "female",
188  location: "LOC-001",
189  healthStatus: "good",
190  lastExamination: new Date("2023-03-20"),
191  rfidTag: "RFID-TREX-001"
192});
193
194// Génération d'un rapport
195const report = jurassicPark.generateParkStatusReport();
196console.log("Jurassic Park Status Report:");
197console.log(`Number of employees: ${report.employeeCount}`);
198console.log(`Number of locations: ${report.locationCount}`);
199console.log(`Number of species: ${report.speciesCount}`);
200console.log(`Number of specimens: ${report.specimenCount}`);
201console.log("Specimen health status:", report.specimenHealthStatus);

Résumé

Les interfaces en TypeScript sont un outil puissant pour modéliser les structures de données et définir des contrats dans le code. Dans le contexte de la gestion du Jurassic Park, elles nous permettent de définir précisément à quoi doivent ressembler les différents systèmes, procédures et éléments du parc.

Fonctionnalité d'interfaceUtilisation au Jurassic Park
Définition de baseStructures de données pour les dinosaures, employés, localisations
Propriétés optionnellesInformations supplémentaires qui peuvent être disponibles ou non
Propriétés readonlyDonnées qui ne devraient pas changer, ex. identifiants génétiques
Interfaces de fonctionDéfinitions de procédures et protocoles de sécurité
Types indexablesBases de données dynamiques de dinosaures et de ressources
Interfaces de classeNormes pour les systèmes de sécurité et de surveillance
Étendre des interfacesHéritage de traits entre espèces de dinosaures
Fusion de déclarationsÉtendre des protocoles avec des procédures supplémentaires

Le Dr Wu résume : « Les interfaces en TypeScript, comme les protocoles de notre parc, garantissent que chaque élément du système fonctionne comme prévu. Elles nous permettent de construire des systèmes complexes avec la certitude que les différents composants fonctionneront ensemble, ce qui est absolument crucial lorsqu'on a affaire à des espèces qui peuvent être dangereuses si quelque chose tourne mal. »

Code de cette leçon : index.ts
1// Interfaces dans le Parc Jurassique
2console.log("Interfaces TypeScript\n");
3
4// ===========================================
5// 1. Interfaces de base
6// ===========================================
7console.log("=== INTERFACES DE BASE ===");
8
9// Interface simple
10interface Dinosaur {
11  name: string;
12  species: string;
13  age: number;
14  weight: number;
15}
16
17const trex: Dinosaur = {
18  name: "Rexy",
19  species: "Tyrannosaurus Rex",
20  age: 5,
21  weight: 8000
22};
23
24console.log("T-Rex:", trex);
25
26// ===========================================
27// 2. Optional properties
28// ===========================================
29console.log("\n=== OPTIONAL PROPERTIES ===");
30
31interface DinosaurWithOptionals {
32  name: string;
33  species: string;
34  age: number;
35  nickname?: string; // optionnel
36  medicalNotes?: string; // optionnel
37}
38
39const blue: DinosaurWithOptionals = {
40  name: "Blue",
41  species: "Velociraptor",
42  age: 3,
43  nickname: "The Beta"
44  // medicalNotes omitted - OK
45};
46
47console.log("Blue:", blue);
48console.log("Has medical notes:", blue.medicalNotes !== undefined);
49
50// ===========================================
51// 3. Readonly properties
52// ===========================================
53console.log("\n=== READONLY PROPERTIES ===");
54
55interface DinosaurWithReadonly {
56  readonly id: string; // cannot be changed
57  readonly dnaSequence: string; // cannot be changed
58  name: string; // can be changed
59  age: number; // can be changed
60}
61
62const dino: DinosaurWithReadonly = {
63  id: "DINO-001",
64  dnaSequence: "ACGT...",
65  name: "Rex",
66  age: 5
67};
68
69// dino.id = "DINO-002"; // Error: Cannot assign to 'id'
70dino.name = "Rexy"; // OK
71dino.age = 6; // OK
72
73console.log("Dinosaur:", dino);
74
75// ===========================================
76// 4. Méthodes dans les interfaces
77// ===========================================
78console.log("\n=== MÉTHODES DANS LES INTERFACES ===");
79
80interface DinosaurWithMethods {
81  name: string;
82  species: string;
83  energy: number;
84
85  // Méthodes
86  roar(): string;
87  eat(amount: number): void;
88  sleep(hours: number): void;
89  getStatus(): string;
90}
91
92class TyrannosaurusRex implements DinosaurWithMethods {
93  name: string;
94  species: string;
95  energy: number;
96
97  constructor(name: string) {
98    this.name = name;
99    this.species = "Tyrannosaurus Rex";
100    this.energy = 100;
101  }
102
103  roar(): string {
104    this.energy -= 5;
105    return `${this.name} roars: ROAAAAAR!`;
106  }
107
108  eat(amount: number): void {
109    this.energy += amount * 2;
110    if (this.energy > 100) this.energy = 100;
111    console.log(`${this.name} ate ${amount}kg. Energy: ${this.energy}`);
112  }
113
114  sleep(hours: number): void {
115    this.energy += hours * 10;
116    if (this.energy > 100) this.energy = 100;
117    console.log(`${this.name} slept ${hours}h. Energy: ${this.energy}`);
118  }
119
120  getStatus(): string {
121    if (this.energy > 70) return "Energetic";
122    if (this.energy > 30) return "Normal";
123    return "Tired";
124  }
125}
126
127const rexy = new TyrannosaurusRex("Rexy");
128console.log(rexy.roar());
129rexy.eat(50);
130console.log("Status:", rexy.getStatus());
131
132// ===========================================
133// 5. Index signatures
134// ===========================================
135console.log("\n=== INDEX SIGNATURES ===");
136
137// Clés dynamiques
138interface DinosaurCollection {
139  [id: string]: Dinosaur;
140}
141
142const collection: DinosaurCollection = {
143  "T-001": trex,
144  "V-001": blue
145};
146
147console.log("Collection:", collection);
148console.log("T-001:", collection["T-001"]);
149
150// Mixed properties
151interface PaddockData {
152  id: string;
153  capacity: number;
154  [key: string]: any; // other dynamic properties
155}
156
157const paddock: PaddockData = {
158  id: "P-01",
159  capacity: 10,
160  securityLevel: "high",
161  temperature: 25,
162  humidity: 60
163};
164
165console.log("Paddock:", paddock);
166
167// ===========================================
168// 6. Functions dans les interfaces
169// ===========================================
170console.log("\n=== FUNCTIONS DANS LES INTERFACES ===");
171
172// Type function
173interface DinosaurFilter {
174  (dino: Dinosaur): boolean;
175}
176
177const carnivoreFilter: DinosaurFilter = (dino) => {
178  return dino.species.includes("saurus") && dino.weight > 1000;
179};
180
181console.log("Is T-Rex carnivore:", carnivoreFilter(trex));
182
183// Interface for functions with properties
184interface DinosaurValidator {
185  (dino: Dinosaur): boolean;
186  errorMessage: string;
187}
188
189const ageValidator: DinosaurValidator = (dino) => {
190  return dino.age >= 0 && dino.age <= 100;
191};
192ageValidator.errorMessage = "Age must be between 0 and 100";
193
194console.log("Validator:", ageValidator(trex));
195console.log("Error message:", ageValidator.errorMessage);
196
197// ===========================================
198// 7. Nested interfaces
199// ===========================================
200console.log("\n=== NESTED INTERFACES ===");
201
202interface Location {
203  latitude: number;
204  longitude: number;
205  altitude: number;
206}
207
208interface Paddock {
209  id: string;
210  name: string;
211  location: Location; // nested interface
212  dimensions: {
213    width: number;
214    length: number;
215    height: number;
216  };
217  dinosaurs: Dinosaur[];
218}
219
220const trexPaddock: Paddock = {
221  id: "P-01",
222  name: "T-Rex Territory",
223  location: {
224    latitude: 22.3964,
225    longitude: -159.5261,
226    altitude: 50
227  },
228  dimensions: {
229    width: 1000,
230    length: 1500,
231    height: 50
232  },
233  dinosaurs: [trex]
234};
235
236console.log("Paddock:", trexPaddock);
237console.log("Location:", trexPaddock.location);
238
239// ===========================================
240// 8. Hybrid types
241// ===========================================
242console.log("\n=== HYBRID TYPES ===");
243
244interface Counter {
245  (start: number): string;
246  interval: number;
247  reset(): void;
248}
249
250function createCounter(): Counter {
251  let count = 0;
252
253  const counter = function(start: number) {
254    count = start;
255    return `Count: ${count}`;
256  } as Counter;
257
258  counter.interval = 1;
259  counter.reset = function() {
260    count = 0;
261  };
262
263  return counter;
264}
265
266const myCounter = createCounter();
267console.log(myCounter(5));
268console.log("Interval:", myCounter.interval);
269myCounter.reset();
270
271// ===========================================
272// 9. Résumé
273// ===========================================
274console.log("\n=== RÉSUMÉ ===");
275console.log("Interfaces de base - object structure");
276console.log("Optional properties - ?");
277console.log("Readonly - immutable properties");
278console.log("Méthodes - functions dans les interfaces");
279console.log("Index signatures - clés dynamiques");
280console.log("Nested interfaces - complex structures");
281
282console.log("\nInterfaces 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. En quoi une interface diffère-t-elle d’un alias de type en TypeScript ?

  2. 2. Comment marquer une propriété d’interface comme optionnelle en TypeScript ?

Tâches pratiques dans le jeu

  • Éditeur de code

    Définis une interface Dinosaur avec les champs name: string, species: string, weight?: number et une méthode roar(): void.

  • Tri horizontal

    Ordonne les éléments d’une définition d’interface TypeScript :

  • Clique dans l'ordre

    Ordonne les éléments de la définition d’un champ readonly dans une interface :

  • Tri vertical

    Ordonne les concepts d’interface du plus simple au plus avancé :

Articles utiles