"Dr. Wu, mamy taką sytuację," mówi zdenerwowany asystent laboratorium. "Wzór DNA, który próbujemy sekwencjonować, może być albo z gatunku drapieżnych, albo roślinożernych dinozaurów, ale potrzebujemy różnych protokołów przetwarzania zależnie od tego, z którym mamy do czynienia."
Dr. Henry Wu, główny genetyk Parku Jurajskiego, odpowiada spokojnie: "To nie problem. Nasze algorytmy sekwencjonowania są wystarczająco inteligentne, by wykryć markery drapieżnictwa w DNA i automatycznie zastosować odpowiedni protokół. To właśnie elastyczność naszego systemu."
Podobnie jak zaawansowane algorytmy sekwencjonowania DNA w Parku Jurajskim, TypeScript posiada mechanizm zwany typami warunkowymi (conditional types), który pozwala na dynamiczne określanie typów w zależności od innych typów. To narzędzie pozwala na tworzenie bardziej elastycznych i precyzyjnych interfejsów API, które dostosowują się do różnych scenariuszy.
Typy warunkowe w TypeScript działają podobnie do wyrażeń warunkowych w JavaScript, ale na poziomie typów. Ich składnia przypomina operator trójskładnikowy:
1T extends U ? X : YTen zapis należy czytać jako: "Jeśli typ T rozszerza (jest zgodny z) typem U, to wynikiem jest typ X, w przeciwnym razie typ Y".
Stwórzmy prostą funkcję, która przetwarza obiekt reprezentujący dinozaura i zwraca różne wyniki w zależności od jego diety:
1type Carnivore = { type: "carnivore"; huntingSkill: number };
2type Herbivore = { type: "herbivore"; foragingSkill: number };
3
4type Dinosaur = Carnivore | Herbivore;
5
6// Typ warunkowy determinujący odpowiedni typ wyniku
7type DinosaurActivity<T extends Dinosaur> = T extends Carnivore
8 ? { activity: "hunting"; preyTargeted: string }
9 : { activity: "grazing"; plantsConsumed: number };
10
11// Funkcja używająca typu warunkowego
12function trackDinosaurActivity<T extends Dinosaur>(dino: T): DinosaurActivity<T> {
13 if (dino.type === "carnivore") {
14 // TypeScript wie, że dla Carnivore powinniśmy zwrócić { activity: "hunting"; preyTargeted: string }
15 return {
16 activity: "hunting",
17 preyTargeted: "Gallimimus"
18 } as DinosaurActivity<T>;
19 } else {
20 // TypeScript wie, że dla Herbivore powinniśmy zwrócić { activity: "grazing"; plantsConsumed: number }
21 return {
22 activity: "grazing",
23 plantsConsumed: 42
24 } as DinosaurActivity<T>;
25 }
26}
27
28// Przykładowe użycie
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); // 42W tym przykładzie typ
DinosaurActivity<T> zmienia się w zależności od tego, czy T rozszerza Carnivore czy nie. Dzięki temu możemy zwracać różne struktury danych, a TypeScript zapewnia pełne wsparcie typów dla obu przypadków.Jedną z kluczowych cech typów warunkowych jest ich zachowanie dystrybutywne w przypadku unii typów. Kiedy stosujemy typ warunkowy do unii typów, warunek jest stosowany do każdego elementu unii oddzielnie.
1type ToArray<T> = T extends any ? T[] : never;
2
3// Z typem prostym wynik jest prosty
4type NumberArray = ToArray<number>; // number[]
5
6// Z unią typów, warunek jest stosowany dystrybutywnie
7type NumberOrStringArray = ToArray<number | string>; // number[] | string[]W drugim przypadku,
ToArray<number | string> jest równoważne ToArray<number> | ToArray<string>, co daje number[] | string[], a nie (number | string)[].Możemy zastosować to do naszego przykładu z dinozaurami:
1// Definiujemy więcej typów dinozaurów
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// Typ warunkowy filtrujący drapieżniki
8type IsPredator<T> = T extends { species: "tyrannosaurus" | "velociraptor" } ? T : never;
9
10// Zastosowanie dystrybutywne na unii typów
11type Predators = IsPredator<TRex | Velociraptor | Triceratops | Brachiosaurus>;
12// Wynik: TRex | Velociraptor
13
14// Możemy też wyfiltrować roślinożerców
15type IsHerbivore<T> = T extends { species: "triceratops" | "brachiosaurus" } ? T : never;
16type Herbivores = IsHerbivore<TRex | Velociraptor | Triceratops | Brachiosaurus>;
17// Wynik: Triceratops | BrachiosaurusTypeScript pozwala na wydobywanie (inferencję) typów z innych typów przy użyciu słowa kluczowego
infer. Jest to szczególnie przydatne w typach warunkowych.1// Typ, który wydobywa typ zwracany przez funkcję
2type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
3
4// Przykład użycia
5function studyDinosaur(specimen: string): { species: string; age: number } {
6 return {
7 species: "Velociraptor",
8 age: 5
9 };
10}
11
12// Wyodrębniony typ zwracany przez funkcję studyDinosaur
13type StudyResult = ReturnType<typeof studyDinosaur>; // { species: string; age: number }W tym przykładzie
infer R mówi TypeScriptowi, aby "wydedukował" typ zwracany przez funkcję i przypisał go do typu R, który jest następnie używany jako wynik typu warunkowego.Możemy stworzyć bardziej skomplikowane przykłady wydobywania typów, na przykład:
1// Typ reprezentujący DNA dinozaura
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// Wyciągamy markery z DNA
14type ExtractMarkers<T> = T extends { markers: infer M } ? M : never;
15
16// Wyciągamy wielkość z markerów
17type ExtractSize<T> = T extends { size: infer S } ? S : never;
18
19// Przykładowe użycie
20type DinosaurMarkers = ExtractMarkers<DinosaurDNA>;
21// { predatory?: boolean; aquatic?: boolean; flying?: boolean; size: "small" | "medium" | "large"; }
22
23type DinosaurSize = ExtractSize<DinosaurMarkers>;
24// "small" | "medium" | "large"Typy warunkowe są szczególnie przydatne w zaawansowanych scenariuszach programowania, takich jak:
1// Przykład API do operacji na dinozaurach
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 // Metoda, która zwraca różne typy zależnie od parametru T
8 analyze: T extends { genomeSequence: string }
9 ? (id: string) => Promise<{ compatibilityScore: number }>
10 : (id: string) => Promise<{ behaviorPrediction: string }>;
11}
12
13// Implementacja dla dinozaurów z sekwencją genomu
14interface GenomeSequencedDino {
15 id: string;
16 name: string;
17 genomeSequence: string;
18}
19
20// Implementacja dla dinozaurów bez sekwencji genomu
21interface BehaviorStudiedDino {
22 id: string;
23 name: string;
24 observedBehaviors: string[];
25}
26
27// Różne implementacje dla różnych typów
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 // Implementacja metody analyze będzie się różnić w zależności od typu 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 // Potrzebny cast ze względu na ograniczenia TypeScript w czasie wykonania
56 };
57}
58
59// Użycie różnych API
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 }1// Pomocniczy typ Exclude - wyklucza typy z unii
2type Exclude<T, U> = T extends U ? never : T;
3
4// Przykład: wykluczanie niebezpiecznych dinozaurów
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// Pomocniczy typ Extract - wyciąga typy spełniające warunek
12type Extract<T, U> = T extends U ? T : never;
13
14// Przykład: wyodrębnianie dinozaurów z epoki jurajskiej
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// Pomocniczy typ NonNullable - usuwa null i undefined z typu
23type NonNullable<T> = T extends null | undefined ? never : T;
24
25// Przykład: upewniamy się, że mamy niepuste dane o dinozaurach
26type PossibleDinoData = { name: string; weight: number } | null | undefined;
27type DefiniteDinoData = NonNullable<PossibleDinoData>;
28// { name: string; weight: number }1// Typ Mapper przekształcający właściwości obiektu z jednego typu na inny
2type MapProperties<T, U> = {
3 [K in keyof T]: T[K] extends object ? MapProperties<T[K], U> : U;
4};
5
6// Przykład: mapowanie wartości liczbowych na stringi
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// Przekształcamy wszystkie liczby na stringi dla celów wyświetlania
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*/Przyjrzyjmy się bardziej złożonemu przykładowi, który wykorzystuje typy warunkowe do modelowania różnych systemów w Parku Jurajskim:
1// --- Podstawowe typy systemów ---
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// Różne typy systemów w parku
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// Unia wszystkich typów systemów
48type ParkSystem = SecuritySystem | PowerSystem | TransportSystem | LifeSupportSystem;
49
50// --- Typy warunkowe dla danych monitorowania ---
51
52/**
53 * Typ warunkowy, który określa, jakie dane monitoringu są istotne dla każdego typu systemu
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 dla nieznanych typów
88
89/**
90 * Typ warunkowy określający zalecane działania dla systemów w różnych stanach
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// --- Klasa monitorująca systemy wykorzystująca typy warunkowe ---
102
103/**
104 * Klasa zarządzająca monitoringiem systemów, używająca typów warunkowych
105 * do zapewnienia typowej poprawności dla różnych systemów
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 // Inicjalizacja monitoringData na podstawie typu systemu
116 this.monitoringData = this.initializeMonitoringData();
117
118 // Inicjalizacja recommendedActions na podstawie typu i statusu systemu
119 this.recommendedActions = this.initializeRecommendedActions();
120 }
121
122 // Metoda pomocnicza do inicjalizacji danych monitorowania
123 private initializeMonitoringData(): MonitoringData<T> {
124 // Implementacja zależna od typu 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(`Nieznany typ systemu: ${(this.system as any).type}`);
153 }
154 }
155
156 // Metoda pomocnicza do inicjalizacji zalecanych działań
157 private initializeRecommendedActions(): RecommendedActions<T> {
158 if (this.system.status === "error") {
159 return {
160 severity: "high",
161 actions: ["Natychmiastowa interwencja techniczna", "Powiadomienie kierownictwa"],
162 escalation: "Dyrektor ds. technicznych",
163 timeToResolve: 60 // minut
164 } as RecommendedActions<T>;
165 } else if (this.system.status === "maintenance") {
166 return {
167 checklistItems: ["Sprawdzenie okablowania", "Test funkcjonalności"],
168 estimatedCompletionTime: new Date(Date.now() + 3600000), // +1 godzina
169 personnelAssigned: ["Tech-1", "Tech-2"]
170 } as RecommendedActions<T>;
171 } else if (this.system.status === "offline") {
172 return {
173 restartProcedures: ["Kontrola zasilania", "Reset systemu"],
174 systemDependencies: ["Główne zasilanie"],
175 estimatedDowntime: 120 // minut
176 } as RecommendedActions<T>;
177 } else {
178 return {
179 routineChecks: ["Codzienny przegląd", "Sprawdzenie logów"],
180 nextMaintenanceDate: new Date(Date.now() + 7 * 24 * 3600000) // +7 dni
181 } as RecommendedActions<T>;
182 }
183 }
184
185 // Metoda aktualizująca dane monitorowania
186 updateMonitoringData(newData: Partial<MonitoringData<T>>): void {
187 this.monitoringData = { ...this.monitoringData, ...newData };
188 console.log(`Zaktualizowano dane monitorowania dla ${this.system.name} (ID: ${this.system.id})`);
189 }
190
191 // Metoda generująca raport systemu
192 generateSystemReport(): string {
193 const report = `
194RAPORT SYSTEMU: ${this.system.name.toUpperCase()} (ID: ${this.system.id})
195=========================================================
196Typ: ${this.system.type}
197Status: ${this.system.status}
198Ostatnia aktualizacja: ${this.system.lastUpdated.toLocaleString()}
199
200SZCZEGÓŁY MONITOROWANIA:
201${this.formatMonitoringData()}
202
203ZALECANE DZIAŁANIA:
204${this.formatRecommendedActions()}
205=========================================================
206Raport wygenerowany: ${new Date().toLocaleString()}
207`;
208
209 return report;
210 }
211
212 // Metoda formatująca dane monitorowania zależnie od typu systemu
213 private formatMonitoringData(): string {
214 if (this.system.type === "security") {
215 const data = this.monitoringData as MonitoringData<SecuritySystem>;
216 return `
217Wykryte naruszenia: ${data.breachDetections}
218Aktywne alarmy: ${data.activeAlarms.length > 0 ? data.activeAlarms.join(", ") : "Brak"}
219Nagrania bezpieczeństwa: ${data.securityFootage.length}
220`;
221 } else if (this.system.type === "power") {
222 const data = this.monitoringData as MonitoringData<PowerSystem>;
223 return `
224Efektywność energetyczna: ${data.energyEfficiency}%
225Szacowany czas na zasilaniu awaryjnym: ${data.estimatedBackupTime} godzin
226Fluktuacje mocy: ${data.powerFluctuations ? "TAK" : "NIE"}
227Rozkład obciążenia: ${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 `
232Aktywne pojazdy: ${data.activeVehicles}
233Liczba pasażerów: ${data.passengerCount}
234Zużycie paliwa: ${data.fuelConsumption} litrów/godz.
235Raporty opóźnień: ${data.delayReports.length > 0
236 ? data.delayReports.map(d => `${d.routeId}: ${d.delay}min`).join(", ")
237 : "Brak opóźnień"}
238`;
239 } else if (this.system.type === "lifesupport") {
240 const data = this.monitoringData as MonitoringData<LifeSupportSystem>;
241 return `
242Warunki w habitatach: ${data.habitatConditions.map(h =>
243 `${h.habitatId} (Temp: ${h.temperature}°C, Wilg: ${h.humidity}%, O2: ${h.oxygenLevel}%)`).join("\n")}
244Wykryte anomalie: ${data.anomalyDetections.length > 0 ? data.anomalyDetections.join(", ") : "Brak"}
245Zużycie zasobów: Woda: ${data.resourceConsumption.water}m³, Prąd: ${data.resourceConsumption.electricity}kWh, Powietrze: ${data.resourceConsumption.air}m³
246`;
247 } else {
248 return `Brak danych monitorowania dla tego typu systemu.`;
249 }
250 }
251
252 // Metoda formatująca zalecane działania zależnie od statusu systemu
253 private formatRecommendedActions(): string {
254 if (this.system.status === "error") {
255 const actions = this.recommendedActions as RecommendedActions<{ status: "error" }>;
256 return `
257Poziom ważności: ${actions.severity}
258Zalecane działania: ${actions.actions.join(", ")}
259Eskalacja do: ${actions.escalation}
260Szacowany czas naprawy: ${actions.timeToResolve} minut
261`;
262 } else if (this.system.status === "maintenance") {
263 const actions = this.recommendedActions as RecommendedActions<{ status: "maintenance" }>;
264 return `
265Elementy do sprawdzenia: ${actions.checklistItems.join(", ")}
266Szacowany czas zakończenia: ${actions.estimatedCompletionTime.toLocaleString()}
267Przydzielony personel: ${actions.personnelAssigned.join(", ")}
268`;
269 } else if (this.system.status === "offline") {
270 const actions = this.recommendedActions as RecommendedActions<{ status: "offline" }>;
271 return `
272Procedury restartu: ${actions.restartProcedures.join(", ")}
273Zależności systemowe: ${actions.systemDependencies.join(", ")}
274Szacowany czas przestoju: ${actions.estimatedDowntime} minut
275`;
276 } else {
277 const actions = this.recommendedActions as RecommendedActions<ParkSystem>;
278 return `
279Rutynowe kontrole: ${actions.routineChecks.join(", ")}
280Następny przegląd: ${actions.nextMaintenanceDate.toLocaleDateString()}
281`;
282 }
283 }
284
285 // Metoda zwracająca zalecane działania
286 getRecommendedActions(): RecommendedActions<T> {
287 return this.recommendedActions;
288 }
289
290 // Metoda aktualizująca status systemu
291 updateSystemStatus(newStatus: SystemStatus): void {
292 (this.system as any).status = newStatus;
293 this.system.lastUpdated = new Date();
294
295 // Aktualizacja zalecanych działań po zmianie statusu
296 this.recommendedActions = this.initializeRecommendedActions();
297
298 console.log(`Zaktualizowano status systemu ${this.system.name} na ${newStatus}`);
299 }
300}
301
302// --- Przykład użycia ---
303
304// Tworzenie przykładowych systemów
305const securitySystem: SecuritySystem = {
306 id: "SEC-001",
307 name: "Główny system bezpieczeństwa",
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: "Główny system zasilania",
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// Tworzenie monitorów dla różnych systemów
330const securityMonitor = new SystemMonitor<SecuritySystem>(securitySystem);
331const powerMonitor = new SystemMonitor<PowerSystem>(powerSystem);
332
333// Aktualizacja danych monitorowania
334securityMonitor.updateMonitoringData({
335 breachDetections: 3,
336 activeAlarms: ["Sektor 7G", "Północne ogrodzenie"],
337 securityFootage: [
338 { cameraId: "CAM-12", timestamp: new Date(), events: ["Ruch w obszarze zastrzeżonym"] }
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// Symulacja awarii systemu zasilania
354powerMonitor.updateSystemStatus("error");
355
356// Generowanie raportów
357console.log(securityMonitor.generateSystemReport());
358console.log(powerMonitor.generateSystemReport());
359
360// Sprawdzamy, czy zalecane działania są poprawnego typu
361const powerActions = powerMonitor.getRecommendedActions();
362console.log(`Poziom ważności awarii zasilania: ${powerActions.severity}`);
363console.log(`Czas do rozwiązania: ${powerActions.timeToResolve} minut`);Ten rozbudowany przykład pokazuje, jak typy warunkowe mogą być wykorzystane do stworzenia skomplikowanego systemu, który dostosowuje swoje zachowanie i strukturę danych w zależności od typu obiektu, nad którym pracuje. Jest to szczególnie przydatne w modelowaniu systemów, które mają wiele wariantów, ale współdzielą pewną wspólną funkcjonalność.
Mimo że typy warunkowe są potężnym narzędziem, mają również pewne ograniczenia:
Ograniczona zdolność wnioskowania - TypeScript ma ograniczoną zdolność wnioskowania o typach w złożonych scenariuszach, szczególnie kiedy używane są typy generyczne razem z typami warunkowymi.
Type erasure w czasie wykonania - podobnie jak inne zaawansowane funkcje typów w TypeScript, typy warunkowe istnieją tylko w czasie kompilacji. W czasie wykonania nie ma informacji o typach warunkowych, co może prowadzić do trudności w implementacji niektórych wzorców.
Złożoność dla początkujących - typy warunkowe, szczególnie w połączeniu z innymi zaawansowanymi funkcjami typów, mogą być trudne do zrozumienia dla osób początkujących w TypeScript.
Typy warunkowe są szczególnie przydatne w następujących scenariuszach:
Tworzenie elastycznych API - gdy chcesz, aby twoje funkcje lub metody zachowywały się różnie w zależności od typu przekazanych argumentów.
Implementacja zaawansowanych narzędzi generycznych - gdy budujesz skomplikowane narzędzia, które muszą działać z różnymi typami danych.
Modelowanie złożonych systemów biznesowych - gdy masz do czynienia z różnymi wariantami obiektów biznesowych, które mają wspólne cechy, ale różnią się w szczegółach.
Tworzenie narzędzi transformacji typów - gdy potrzebujesz mapować jedne typy na inne w sposób warunkowy.
Typy warunkowe w TypeScript są zaawansowanym, ale niezwykle przydatnym narzędziem, które pozwala na tworzenie elastycznych i typowo bezpiecznych API. Podobnie jak genetycy w Parku Jurajskim mogą dostosować swoje protokoły do różnych typów DNA dinozaurów, typy warunkowe pozwalają nam dostosować zachowanie naszego kodu do różnych typów danych, zachowując przy tym pełne wsparcie dla statycznego typowania.
W następnych ćwiczeniach będziemy kontynuować eksplorację zaawansowanych funkcji systemu typów TypeScript i zobaczymy, jak mogą one pomóc nam w tworzeniu bezpieczniejszego, bardziej elastycznego i łatwiejszego w utrzymaniu kodu.