Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Conditional types (Typy warunkowe)

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

Podstawy typów warunkowych

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 : Y

Ten 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".

Prosty przykład

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); // 42

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

Dystrybutywne typy warunkowe

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 | Brachiosaurus

Inferred Types (Wnioskowanie typów)

TypeScript 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"

Praktyczne zastosowania typów warunkowych

Typy warunkowe są szczególnie przydatne w zaawansowanych scenariuszach programowania, takich jak:

1. Tworzenie elastycznych komponentów API

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 }

2. Implementacja wzorców typu

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 }

3. Tworzenie mapperów typów

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*/

Zaawansowany przykład: System typów dla infrastruktury Parku Jurajskiego

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}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ść.

Ograniczenia typów warunkowych

Mimo że typy warunkowe są potężnym narzędziem, mają również pewne ograniczenia:

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

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

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

Kiedy używać typów warunkowych?

Typy warunkowe są szczególnie przydatne w następujących scenariuszach:

  1. Tworzenie elastycznych API - gdy chcesz, aby twoje funkcje lub metody zachowywały się różnie w zależności od typu przekazanych argumentów.

  2. Implementacja zaawansowanych narzędzi generycznych - gdy budujesz skomplikowane narzędzia, które muszą działać z różnymi typami danych.

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

  4. Tworzenie narzędzi transformacji typów - gdy potrzebujesz mapować jedne typy na inne w sposób warunkowy.

Podsumowanie

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.

Przejdź do CodeWorlds