Gratulacje - dotarłeś do końcowego projektu modułu o podstawach TypeScript! Przez ostatnie lekcje poznałeś potężne narzędzia typowania: od podstawowych typów, przez tablice, krotki i enumy, aż po interfejsy, typy unii i aliasy. Teraz nadszedł czas, aby połączyć tę wiedzę w jeden kompleksowy projekt.
Wyobraź sobie, że zespół programistów Parku Jurajskiego w końcu postanowił przepisać swoje systemy z czystego JavaScriptu na TypeScript. Po zbyt wielu incydentach spowodowanych błędami typów - od źle przekazanych parametrów zasilania, przez pomylone gatunki dinozaurów, po awarie systemu bezpieczeństwa z powodu
undefined - zarząd podjął decyzję: "Nigdy więcej!". Twoim zadaniem jest stworzenie fundamentu nowego, w pełni typowanego systemu zarządzania parkiem.W tym projekcie zbudujesz kompletny system typów i interfejsów dla Parku Jurajskiego, który zapewni bezpieczeństwo typów na każdym poziomie aplikacji.
Stworzysz typowany system zarządzania Parkiem Jurajskim składający się z czterech obszarów:
Pierwszy obszar to precyzyjny model danych opisujący dinozaury w parku. Używamy enumów dla stałych wartości, interfejsów dla struktury obiektów i aliasów typów dla złożonych kombinacji.
1// Enumy - stałe wartości w systemie
2enum Diet {
3 Carnivore = "mięsożerca",
4 Herbivore = "roślinożerca",
5 Omnivore = "wszystkożerca",
6 Piscivore = "rybożerca"
7}
8
9enum Era {
10 Triassic = "trias",
11 Jurassic = "jura",
12 Cretaceous = "kreda"
13}
14
15enum ThreatLevel {
16 Minimal = 1,
17 Low = 2,
18 Moderate = 3,
19 High = 4,
20 Extreme = 5
21}
22
23enum DinoStatus {
24 Healthy = "zdrowy",
25 Sick = "chory",
26 Sleeping = "śpi",
27 Feeding = "je",
28 Agitated = "pobudzony",
29 Escaped = "uciekł"
30}
31
32// Typ aliasowy - gatunek dinozaura
33type Species =
34 | "Tyrannosaurus Rex"
35 | "Velociraptor"
36 | "Triceratops"
37 | "Brachiosaurus"
38 | "Stegosaurus"
39 | "Pteranodon"
40 | "Dilophosaurus"
41 | "Gallimimus"
42 | "Parasaurolophus"
43 | "Ankylosaurus";
44
45// Interfejs - dane medyczne dinozaura
46interface DinoHealth {
47 weight: number; // w kilogramach
48 height: number; // w metrach
49 heartRate: number; // uderzenia na minutę
50 temperature: number; // stopnie Celsjusza
51 lastFed: Date;
52 lastCheckup: Date;
53 conditions: string[]; // lista stanów zdrowotnych
54}
55
56// Interfejs - pełny profil dinozaura
57interface Dinosaur {
58 id: string;
59 name: string;
60 species: Species; // Typ aliasowy
61 diet: Diet; // Enum
62 era: Era; // Enum
63 threatLevel: ThreatLevel; // Enum
64 status: DinoStatus; // Enum
65 health: DinoHealth; // Zagnieżdżony interfejs
66 sectorId: string;
67 trackingChipActive: boolean;
68 dateAdded: Date;
69}
70
71// Typ unii - filtr wyszukiwania dinozaurów
72type DinoSearchCriteria =
73 | { by: "species"; value: Species }
74 | { by: "diet"; value: Diet }
75 | { by: "threatLevel"; minLevel: ThreatLevel }
76 | { by: "status"; value: DinoStatus }
77 | { by: "sector"; sectorId: string };
78
79// Funkcja z typami
80function searchDinosaurs(
81 dinosaurs: Dinosaur[],
82 criteria: DinoSearchCriteria
83): Dinosaur[] {
84 switch (criteria.by) {
85 case "species":
86 return dinosaurs.filter(d => d.species === criteria.value);
87 case "diet":
88 return dinosaurs.filter(d => d.diet === criteria.value);
89 case "threatLevel":
90 return dinosaurs.filter(d => d.threatLevel >= criteria.minLevel);
91 case "status":
92 return dinosaurs.filter(d => d.status === criteria.value);
93 case "sector":
94 return dinosaurs.filter(d => d.sectorId === criteria.sectorId);
95 }
96}Zauważ, jak TypeScript wymusza kompletność obsługi w
switch - jeśli dodasz nowy wariant do DinoSearchCriteria, kompilator przypomni Ci o obsłużeniu nowego przypadku.Drugi obszar to model infrastruktury parku - sektory, ogrodzenia, systemy zasilania i monitoring.
1// Typ unii - rodzaj ogrodzenia
2type FenceType = "electric" | "concrete" | "reinforced" | "moat";
3
4// Interfejs ogrodzenia
5interface Fence {
6 id: string;
7 type: FenceType;
8 voltage: number; // 0 dla nie-elektrycznych
9 integrity: number; // 0-100%
10 lastMaintenance: Date;
11 requiresRepair: boolean;
12}
13
14// Enum - status sektora
15enum SectorStatus {
16 Operational = "operacyjny",
17 Maintenance = "konserwacja",
18 Lockdown = "blokada",
19 Evacuated = "ewakuowany",
20 Offline = "offline"
21}
22
23// Interfejs - system zasilania
24interface PowerSystem {
25 mainPowerLevel: number; // 0-100%
26 backupPowerLevel: number; // 0-100%
27 isBackupActive: boolean;
28 solarPanelsOnline: number; // liczba aktywnych paneli
29 generatorStatus: "running" | "standby" | "failed";
30}
31
32// Interfejs sektora z zagnieżdżonymi typami
33interface Sector {
34 id: string;
35 name: string;
36 status: SectorStatus;
37 dinosaurs: Dinosaur[];
38 fences: Fence[];
39 power: PowerSystem;
40 capacity: number; // maksymalna liczba dinozaurów
41 securityLevel: ThreatLevel;
42 staff: StaffMember[];
43}
44
45// Typ aliasowy - rola pracownika
46type StaffRole =
47 | "weterynarz"
48 | "strażnik"
49 | "technik"
50 | "naukowiec"
51 | "kierownik"
52 | "przewodnik";
53
54// Interfejs pracownika
55interface StaffMember {
56 id: string;
57 name: string;
58 role: StaffRole;
59 assignedSector: string;
60 clearanceLevel: ThreatLevel; // Reużycie enuma w innym kontekście
61 onDuty: boolean;
62 specializations: string[];
63}
64
65// Typ - podsumowanie stanu sektora
66type SectorSummary = {
67 sectorId: string;
68 sectorName: string;
69 status: SectorStatus;
70 dinoCount: number;
71 avgFenceIntegrity: number;
72 powerStatus: "stabilne" | "niskie" | "krytyczne";
73 staffOnDuty: number;
74 alerts: number;
75};
76
77// Funkcja generująca podsumowanie
78function getSectorSummary(sector: Sector): SectorSummary {
79 const avgFence = sector.fences.reduce(
80 (sum, f) => sum + f.integrity, 0
81 ) / sector.fences.length;
82
83 const powerStatus: SectorSummary["powerStatus"] =
84 sector.power.mainPowerLevel > 60 ? "stabilne" :
85 sector.power.mainPowerLevel > 30 ? "niskie" : "krytyczne";
86
87 return {
88 sectorId: sector.id,
89 sectorName: sector.name,
90 status: sector.status,
91 dinoCount: sector.dinosaurs.length,
92 avgFenceIntegrity: Math.round(avgFence),
93 powerStatus,
94 staffOnDuty: sector.staff.filter(s => s.onDuty).length,
95 alerts: 0
96 };
97}Interfejsy budują się hierarchicznie -
Sector zawiera tablice Dinosaur[], Fence[] i StaffMember[], tworząc bogaty model danych.Trzeci obszar to system zdarzeń i alertów bezpieczeństwa, który intensywnie korzysta z typów unii, aby precyzyjnie modelować różne rodzaje incydentów.
1// Typy unii dla zdarzeń bezpieczeństwa - każde zdarzenie ma inną strukturę
2type SecurityEvent =
3 | {
4 type: "fence_breach";
5 sectorId: string;
6 fenceId: string;
7 dinosaurId: string;
8 severity: "warning" | "critical";
9 }
10 | {
11 type: "power_outage";
12 sectorId: string;
13 affectedSystems: string[];
14 backupActivated: boolean;
15 }
16 | {
17 type: "dinosaur_escape";
18 dinosaurId: string;
19 species: Species;
20 lastKnownSector: string;
21 threatLevel: ThreatLevel;
22 }
23 | {
24 type: "sensor_failure";
25 sectorId: string;
26 sensorIds: string[];
27 possibleCause: "damage" | "power" | "sabotage" | "unknown";
28 }
29 | {
30 type: "unauthorized_access";
31 location: string;
32 personId: string | null;
33 timestamp: Date;
34 };
35
36// Enum - priorytet alertu
37enum AlertPriority {
38 Info = "INFO",
39 Warning = "UWAGA",
40 Danger = "ZAGROŻENIE",
41 Critical = "KRYTYCZNY",
42 Evacuation = "EWAKUACJA"
43}
44
45// Interfejs alertu
46interface SecurityAlert {
47 id: string;
48 event: SecurityEvent;
49 priority: AlertPriority;
50 createdAt: Date;
51 acknowledgedBy: string | null; // ID pracownika lub null
52 resolved: boolean;
53 notes: string[];
54}
55
56// Typ - protokół odpowiedzi na zagrożenie
57type ResponseProtocol = {
58 alertId: string;
59 actions: ProtocolAction[];
60 estimatedTime: number; // w minutach
61 requiredClearance: ThreatLevel;
62};
63
64// Typ unii - akcja protokołu
65type ProtocolAction =
66 | { action: "lockdown"; sectors: string[] }
67 | { action: "evacuate"; sectors: string[]; priority: "staff" | "visitors" | "all" }
68 | { action: "deploy_team"; teamType: StaffRole; targetSector: string }
69 | { action: "activate_backup"; systems: string[] }
70 | { action: "track_dinosaur"; dinosaurId: string; useHelicopter: boolean };
71
72// Funkcja obsługi zdarzenia z type narrowing
73function handleSecurityEvent(event: SecurityEvent): ResponseProtocol {
74 const actions: ProtocolAction[] = [];
75 let estimatedTime = 10;
76 let requiredClearance = ThreatLevel.Moderate;
77
78 switch (event.type) {
79 case "fence_breach":
80 actions.push({ action: "lockdown", sectors: [event.sectorId] });
81 actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
82 if (event.severity === "critical") {
83 actions.push({ action: "evacuate", sectors: [event.sectorId], priority: "visitors" });
84 requiredClearance = ThreatLevel.High;
85 }
86 break;
87
88 case "dinosaur_escape":
89 actions.push({ action: "track_dinosaur", dinosaurId: event.dinosaurId, useHelicopter: event.threatLevel >= ThreatLevel.High });
90 actions.push({ action: "lockdown", sectors: [event.lastKnownSector] });
91 actions.push({ action: "deploy_team", teamType: "strażnik", targetSector: event.lastKnownSector });
92 estimatedTime = 60;
93 requiredClearance = ThreatLevel.Extreme;
94 break;
95
96 case "power_outage":
97 actions.push({ action: "activate_backup", systems: event.affectedSystems });
98 actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
99 if (!event.backupActivated) {
100 actions.push({ action: "lockdown", sectors: [event.sectorId] });
101 requiredClearance = ThreatLevel.High;
102 }
103 break;
104
105 case "sensor_failure":
106 actions.push({ action: "deploy_team", teamType: "technik", targetSector: event.sectorId });
107 if (event.possibleCause === "sabotage") {
108 actions.push({ action: "lockdown", sectors: [event.sectorId] });
109 requiredClearance = ThreatLevel.Extreme;
110 }
111 break;
112
113 case "unauthorized_access":
114 actions.push({ action: "deploy_team", teamType: "strażnik", targetSector: event.location });
115 break;
116 }
117
118 return {
119 alertId: `PROTOCOL-${Date.now()}`,
120 actions,
121 estimatedTime,
122 requiredClearance
123 };
124}Type narrowing w
switch pozwala TypeScriptowi automatycznie zawęzić typ zdarzenia - w bloku case "fence_breach" TypeScript wie, że event ma pola fenceId i severity.Ostatni obszar to centralny system, który łączy wszystkie powyższe typy w spójny interfejs zarządzania całym parkiem.
1// Interfejs całego parku
2interface JurassicPark {
3 name: string;
4 sectors: Sector[];
5 totalDinosaurs: number;
6 activeAlerts: SecurityAlert[];
7 operationalStatus: "open" | "restricted" | "closed" | "emergency";
8}
9
10// Typ - raport dzienny
11type DailyReport = {
12 date: string;
13 parkStatus: JurassicPark["operationalStatus"]; // Reużycie typu z interfejsu
14 sectorSummaries: SectorSummary[];
15 incidentCount: number;
16 resolvedIncidents: number;
17 dinosaurStats: {
18 total: number;
19 byDiet: Record<Diet, number>; // Record z enumem jako kluczem
20 byStatus: Record<DinoStatus, number>;
21 escaped: number;
22 };
23 staffStats: {
24 totalOnDuty: number;
25 byRole: Record<StaffRole, number>; // Record z type alias jako kluczem
26 };
27 recommendations: string[];
28};
29
30// Funkcja generująca raport
31function generateDailyReport(park: JurassicPark): DailyReport {
32 // Inicjalizacja liczników z enumami
33 const byDiet: Record<Diet, number> = {
34 [Diet.Carnivore]: 0,
35 [Diet.Herbivore]: 0,
36 [Diet.Omnivore]: 0,
37 [Diet.Piscivore]: 0
38 };
39
40 const byStatus: Record<DinoStatus, number> = {
41 [DinoStatus.Healthy]: 0,
42 [DinoStatus.Sick]: 0,
43 [DinoStatus.Sleeping]: 0,
44 [DinoStatus.Feeding]: 0,
45 [DinoStatus.Agitated]: 0,
46 [DinoStatus.Escaped]: 0
47 };
48
49 // Zliczanie dinozaurów
50 const allDinos = park.sectors.flatMap(s => s.dinosaurs);
51 allDinos.forEach(dino => {
52 byDiet[dino.diet]++;
53 byStatus[dino.status]++;
54 });
55
56 // Podsumowania sektorów
57 const sectorSummaries = park.sectors.map(getSectorSummary);
58
59 // Rekomendacje na podstawie danych
60 const recommendations: string[] = [];
61
62 if (byStatus[DinoStatus.Escaped] > 0) {
63 recommendations.push(`KRYTYCZNE: ${byStatus[DinoStatus.Escaped]} dinozaurów uciekło!`);
64 }
65
66 if (byStatus[DinoStatus.Agitated] > allDinos.length * 0.3) {
67 recommendations.push("UWAGA: Ponad 30% dinozaurów jest pobudzonych - sprawdzić przyczynę");
68 }
69
70 const lowPowerSectors = sectorSummaries.filter(s => s.powerStatus === "krytyczne");
71 if (lowPowerSectors.length > 0) {
72 recommendations.push(
73 `Krytyczny poziom zasilania w sektorach: ${lowPowerSectors.map(s => s.sectorName).join(", ")}`
74 );
75 }
76
77 return {
78 date: new Date().toISOString().split("T")[0],
79 parkStatus: park.operationalStatus,
80 sectorSummaries,
81 incidentCount: park.activeAlerts.length,
82 resolvedIncidents: park.activeAlerts.filter(a => a.resolved).length,
83 dinosaurStats: {
84 total: allDinos.length,
85 byDiet,
86 byStatus,
87 escaped: byStatus[DinoStatus.Escaped]
88 },
89 staffStats: {
90 totalOnDuty: park.sectors.flatMap(s => s.staff).filter(s => s.onDuty).length,
91 byRole: {
92 weterynarz: 0, strażnik: 0, technik: 0,
93 naukowiec: 0, kierownik: 0, przewodnik: 0
94 }
95 },
96 recommendations
97 };
98}Przy projektowaniu systemu typów pamiętaj o kluczowych zasadach:
Używaj enumów dla stałych zbiorów wartości:
1// Dobrze - wartości są stałe i znane z góry
2enum Diet { Carnivore = "mięsożerca", Herbivore = "roślinożerca" }
3
4// Źle - zbyt dynamiczne dane dla enuma
5enum DinosaurName { Rex = "Rex", Blue = "Blue" } // Imiona mogą być dowolneInterfejsy dla struktur obiektów, aliasy dla unii i kombinacji:
1// Interfejs - opisuje kształt obiektu
2interface Dinosaur {
3 name: string;
4 species: Species;
5}
6
7// Alias - łączy typy, tworzy warianty
8type SearchResult = Dinosaur | null;
9type DinoFilter = (dino: Dinosaur) => boolean;
10type DinoOrError = { success: true; data: Dinosaur } | { success: false; error: string };Typy unii dyskryminowane (discriminated unions) dla zdarzeń:
1// Pole "type" pozwala TypeScriptowi rozróżniać warianty
2type Event =
3 | { type: "A"; fieldA: string }
4 | { type: "B"; fieldB: number };
5
6function handle(event: Event) {
7 if (event.type === "A") {
8 // TypeScript wie, że tu jest fieldA
9 console.log(event.fieldA);
10 }
11}Record do tworzenia mapowań z enumów:
1// Automatyczna mapa enum → wartość
2const dietLabels: Record<Diet, string> = {
3 [Diet.Carnivore]: "Mięsożerca - karmić mięsem",
4 [Diet.Herbivore]: "Roślinożerca - karmić roślinami",
5 [Diet.Omnivore]: "Wszystkożerca - dieta mieszana",
6 [Diet.Piscivore]: "Rybożerca - karmić rybami"
7};Ten projekt to Twoja szansa na udowodnienie, że rozumiesz moc systemu typów TypeScript. W prawdziwym Parku Jurajskim każda pomyłka może kosztować życie - a TypeScript to Twoje ogrodzenie chroniące przed błędami typów. Jak powiedział Dr. Alan Grant: "Dinozaury i człowiek - dwa gatunki oddzielone przez 65 milionów lat ewolucji - nagle znów muszą żyć obok siebie." A TypeScript sprawia, że ta koegzystencja jest bezpieczna i przewidywalna.
Stwórz swój typowany system zarządzania w edytorze poniżej. Zdefiniuj przynajmniej trzy enumy, cztery interfejsy, dwa typy unii i jedną funkcję wykorzystującą te typy.