Kurs JavaScript i TypeScript · Moduł 6: Podstawy TypeScript

Typy złożone (array, tuple, enum)

8 min czytania
W tej lekcji7

Witaj ponownie w Parku Jurajskim! Po zapoznaniu się z podstawowymi typami danych w TypeScript, jesteśmy gotowi, aby przejść do bardziej zaawansowanych struktur. W dzisiejszej lekcji Dr. Wu zabierze nas do laboratorium, gdzie będziemy eksperymentować z bardziej złożonymi typami danych.

Tak jak w Parku Jurajskim nie wystarczy znać pojedynczych gatunków - trzeba rozumieć, jak funkcjonują całe ekosystemy i hierarchie dinozaurów - tak w TypeScript nie wystarczy znać tylko typy podstawowe. Musimy nauczyć się organizować dane w bardziej złożone struktury.

Typy tablicowe (Array)

Tablice w TypeScript są jak wybiegi w Parku Jurajskim - służą do przechowywania kolekcji podobnych elementów. Możemy zdefiniować typ tablicy na dwa sposoby:

1// Definiowanie tablicy dinozaurów
2let dinozaury: string[] = ["Tyrannosaurus", "Velociraptor", "Triceratops"];
3
4// Alternatywna notacja
5let dinozauryAlt: Array<string> = ["Brachiosaurus", "Stegosaurus", "Ankylosaurus"];
6
7// Tablica liczb - np. wagi dinozaurów w kilogramach
8let wagi: number[] = [7500, 350, 8500, 35000, 5500];
9
10// Dostęp do elementów tablicy
11console.log(`Pierwszy dinozaur: ${dinozaury[0]}`); // Tyrannosaurus
12console.log(`Liczba dinozaurów: ${dinozaury.length}`); // 3
13
14// Dodawanie do tablicy
15dinozaury.push("Spinosaurus");
16console.log(`Teraz mamy ${dinozaury.length} dinozaurów`); // 4
17
18// Iteracja po tablicy
19dinozaury.forEach(dino => {
20  console.log(`Badanie zachowania: ${dino}`);
21});

Tablice mogą również przechowywać bardziej złożone obiekty:

1// Definiowanie interfejsu dla obiektu dinozaura
2interface Dinozaur {
3  id: number;
4  nazwa: string;
5  gatunek: string;
6  jestDrapieżnikiem: boolean;
7  wiekMlnLat: number;
8}
9
10// Tablica obiektów dinozaurów
11const kolekcjaOkazów: Dinozaur[] = [
12  { id: 1, nazwa: "Rex", gatunek: "Tyrannosaurus", jestDrapieżnikiem: true, wiekMlnLat: 68 },
13  { id: 2, nazwa: "Blue", gatunek: "Velociraptor", jestDrapieżnikiem: true, wiekMlnLat: 75 },
14  { id: 3, nazwa: "Trixie", gatunek: "Triceratops", jestDrapieżnikiem: false, wiekMlnLat: 68 }
15];
16
17// Filtrowanie tablicy - znajdź wszystkie drapieżniki
18const drapieżniki = kolekcjaOkazów.filter(dino => dino.jestDrapieżnikiem);
19console.log(`Liczba drapieżników: ${drapieżniki.length}`); // 2
20
21// Przekształcanie tablicy - pozyskaj tylko nazwy dinozaurów
22const nazwyDinozaurów = kolekcjaOkazów.map(dino => dino.nazwa);
23console.log(`Nazwy okazów: ${nazwyDinozaurów.join(", ")}`); // "Rex, Blue, Trixie"

Tablice wielowymiarowe

Tablice wielowymiarowe można wykorzystać do reprezentowania bardziej złożonych danych, takich jak mapa wybiegów w Parku Jurajskim:

1// Dwuwymiarowa tablica reprezentująca mapę Parku Jurajskiego
2// 0 = teren niedostępny, 1 = ścieżka, 2 = wybieg roślinożerców, 3 = wybieg drapieżników
3const mapaParkuJurajskiego: number[][] = [
4  [0, 0, 2, 2, 2, 0, 0],
5  [0, 1, 1, 1, 1, 1, 0],
6  [0, 1, 3, 3, 3, 1, 0],
7  [0, 1, 1, 1, 1, 1, 0],
8  [0, 0, 2, 2, 2, 0, 0]
9];
10
11// Sprawdzanie, co znajduje się na danej pozycji
12function sprawdźTeren(x: number, y: number): string {
13  const teren = mapaParkuJurajskiego[y][x];
14  switch (teren) {
15    case 0: return "Teren niedostępny";
16    case 1: return "Ścieżka dla zwiedzających";
17    case 2: return "Wybieg dinozaurów roślinożernych";
18    case 3: return "UWAGA! Wybieg drapieżników!";
19    default: return "Nieznany teren";
20  }
21}
22
23console.log(sprawdźTeren(2, 2)); // "UWAGA! Wybieg drapieżników!"

Tablice o różnych typach elementów - Tuple (krotki)

Krotki (Tuple) to specjalny rodzaj tablicy, w której znamy dokładną liczbę elementów i ich typy. To jak transport dinozaurów - musi być ściśle określony typ pojazdu i specjalne zabezpieczenia odpowiednie dla konkretnego gatunku.

1// Krotka reprezentująca dane pracownika: [id, imię, stanowisko, poziom dostępu]
2type PracownikParku = [number, string, string, number];
3
4const pracownicy: PracownikParku[] = [
5  [1, "John Hammond", "Dyrektor", 5],
6  [2, "Dr. Alan Grant", "Paleontolog", 4],
7  [3, "Dr. Ellie Sattler", "Paleobotanik", 4],
8  [4, "Dennis Nedry", "Programista", 3]
9];
10
11// Krotka z różnymi typami elementów
12// Format: [kod DNA, gatunek, wiek w latach, czy jest drapieżnikiem]
13type KodGenetyczny = [string, string, number, boolean];
14
15const kodTRex: KodGenetyczny = ["ACGTTCAGC", "Tyrannosaurus Rex", 68, true];
16
17// Destrukturyzacja krotki
18const [dnaKod, gatunek, wiek, drapieżnik] = kodTRex;
19console.log(`DNA: ${dnaKod}, Gatunek: ${gatunek}`);
20
21// Opcjonalne elementy krotki (od TypeScript 3.0)
22// Format: [id, nazwa, opcjonalny tag RFID]
23type OkazMuzealny = [number, string, string?];
24
25const okazDinozaura: OkazMuzealny = [42, "Czaszka Velociraptora"];
26const okazZTagiem: OkazMuzealny = [43, "Jajo dinozaura", "RFID-J-43"];

Typy wyliczeniowe (Enum)

Typy wyliczeniowe (Enum) w TypeScript są jak kategorie bezpieczeństwa w Parku Jurajskim - pomagają definiować zbiór nazwanych stałych, z których można korzystać w kodzie.

1// Definiowanie typu wyliczeniowego dla kategorii zagrożeń
2enum KategoriaZagrożenia {
3  Niskie,      // 0
4  Umiarkowane, // 1
5  Wysokie,     // 2
6  Krytyczne,   // 3
7  PoziomCzerwony // 4
8}
9
10// Użycie enum
11let zagrożenieWybieguTRex: KategoriaZagrożenia = KategoriaZagrożenia.Wysokie;
12
13if (zagrożenieWybieguTRex >= KategoriaZagrożenia.Wysokie) {
14  console.log("Zalecane podwójne ogrodzenie elektryczne!");
15}
16
17// Dostęp po wartości
18const kategoriaOpis = (poziom: KategoriaZagrożenia): string => {
19  switch (poziom) {
20    case KategoriaZagrożenia.Niskie: return "Standardowe środki ostrożności";
21    case KategoriaZagrożenia.Umiarkowane: return "Zwiększona czujność";
22    case KategoriaZagrożenia.Wysokie: return "Podwójne zabezpieczenia";
23    case KategoriaZagrożenia.Krytyczne: return "Zakaz wstępu dla zwiedzających";
24    case KategoriaZagrożenia.PoziomCzerwony: return "Ewakuacja parku!";
25    default: return "Nieznany poziom zagrożenia";
26  }
27};
28
29console.log(kategoriaOpis(zagrożenieWybieguTRex)); // "Podwójne zabezpieczenia"
30
31// Enum z określonymi wartościami
32enum StatusDinozaura {
33  Nieaktywny = 0,
34  OgraniczonaAktywność = 5,
35  Aktywny = 10,
36  BardzoAktywny = 15,
37  NiezbędnaInterwencja = 20
38}
39
40let statusRexT: StatusDinozaura = StatusDinozaura.BardzoAktywny;
41console.log(`Poziom aktywności T-Rex: ${statusRexT}`); // 15
42
43// Enum tekstowy
44enum GatunekDinozaura {
45  TRex = "Tyrannosaurus Rex",
46  Raptor = "Velociraptor",
47  Tric = "Triceratops",
48  Stego = "Stegosaurus",
49  Brach = "Brachiosaurus"
50}
51
52let ulubieniecPubliczności: GatunekDinozaura = GatunekDinozaura.TRex;
53console.log(`Dzisiejsza atrakcja: ${ulubieniecPubliczności}`); // "Tyrannosaurus Rex"

Const Enum

Const Enum to optymalizacja TypeScript, która eliminuje obiekt enum w czasie kompilacji, zastępując odwołania do enum bezpośrednio wartościami.

1// Zdefiniowanie Const Enum
2const enum StrefaBezpieczeństwa {
3  Publiczna = 1,
4  Pracownicy = 2,
5  OgraniczonyDostęp = 3,
6  Laboratoria = 4,
7  Zagrożenie = 5
8}
9
10// Wykorzystanie Const Enum
11function sprawdźUprawnienia(poziomDostępu: number, wymaganaStrefa: StrefaBezpieczeństwa): boolean {
12  return poziomDostępu >= wymaganaStrefa;
13}
14
15// Wywołanie z wartościami enum
16const dostępDrWu = sprawdźUprawnienia(5, StrefaBezpieczeństwa.Laboratoria); // true
17const dostępPracownika = sprawdźUprawnienia(2, StrefaBezpieczeństwa.Zagrożenie); // false

Praktyczne zastosowanie typów złożonych

Przyjrzyjmy się, jak możemy wykorzystać te typy złożone do zbudowania systemu zarządzania Parkiem Jurajskim:

1// Definiowanie interfejsów i typów
2enum StatusZdrowia {
3  Doskonały = "doskonały",
4  Dobry = "dobry",
5  Wymagauwagi = "wymaga uwagi",
6  Chory = "chory",
7  Krytyczny = "krytyczny"
8}
9
10enum KlasyfikacjaDiety {
11  Roślinożerca = "roślinożerca",
12  Wszystkożerca = "wszystkożerca",
13  Drapieżnik = "drapieżnik"
14}
15
16interface DaneWitalne {
17  temperatura: number;
18  tętno: number;
19  ciśnienieKrwi: [number, number]; // Krotka [skurczowe, rozkurczowe]
20  poziomStresu: number; // 1-10
21}
22
23interface Dinozaur {
24  id: number;
25  nazwa: string;
26  gatunek: string;
27  klasyfikacjaDiety: KlasyfikacjaDiety;
28  wiek: number;
29  waga: number;
30  status: StatusZdrowia;
31  daneWitalne: DaneWitalne;
32  aktywnośćDziennaArray: number[]; // Poziom aktywności w okresach 3-godzinnych (8 wartości dla pełnej doby)
33}
34
35// Tworzenie systemu zarządzania dinozaurami
36class SystemZarządzaniaDinozaurami {
37  private dinozaury: Dinozaur[] = [];
38
39  dodajDinozaura(dino: Dinozaur): void {
40    this.dinozaury.push(dino);
41    console.log(`Dodano dinozaura: ${dino.nazwa} (${dino.gatunek})`);
42  }
43
44  znajdźDinozaura(id: number): Dinozaur | undefined {
45    return this.dinozaury.find(d => d.id === id);
46  }
47
48  aktualizujStatusZdrowia(id: number, nowyStatus: StatusZdrowia): boolean {
49    const dino = this.znajdźDinozaura(id);
50    if (dino) {
51      dino.status = nowyStatus;
52      console.log(`Zaktualizowano status zdrowia ${dino.nazwa} na: ${nowyStatus}`);
53      return true;
54    }
55    return false;
56  }
57
58  pobierzDrapieżniki(): Dinozaur[] {
59    return this.dinozaury.filter(d => d.klasyfikacjaDiety === KlasyfikacjaDiety.Drapieżnik);
60  }
61
62  pobierzDinozauryWymagająceUwagi(): Dinozaur[] {
63    return this.dinozaury.filter(d =>
64      d.status === StatusZdrowia.Wymagauwagi ||
65      d.status === StatusZdrowia.Chory ||
66      d.status === StatusZdrowia.Krytyczny
67    );
68  }
69
70  obliczŚrednięAktywności(id: number): number | null {
71    const dino = this.znajdźDinozaura(id);
72    if (dino) {
73      const suma = dino.aktywnośćDziennaArray.reduce((acc, val) => acc + val, 0);
74      return suma / dino.aktywnośćDziennaArray.length;
75    }
76    return null;
77  }
78
79  przygotujRaportDzienny(): [string, number, StatusZdrowia][] {
80    // Zwraca krotkę [nazwa, id, status] dla każdego dinozaura
81    return this.dinozaury.map(d => [d.nazwa, d.id, d.status]);
82  }
83}
84
85// Przykładowe użycie
86const systemJP = new SystemZarządzaniaDinozaurami();
87
88// Dodanie dinozaurów do systemu
89systemJP.dodajDinozaura({
90  id: 1,
91  nazwa: "Rexy",
92  gatunek: "Tyrannosaurus Rex",
93  klasyfikacjaDiety: KlasyfikacjaDiety.Drapieżnik,
94  wiek: 7,
95  waga: 8000,
96  status: StatusZdrowia.Dobry,
97  daneWitalne: {
98    temperatura: 38.2,
99    tętno: 70,
100    ciśnienieKrwi: [140, 90],
101    poziomStresu: 3
102  },
103  aktywnośćDziennaArray: [2, 1, 0, 0, 1, 3, 5, 2]
104});
105
106systemJP.dodajDinozaura({
107  id: 2,
108  nazwa: "Blue",
109  gatunek: "Velociraptor",
110  klasyfikacjaDiety: KlasyfikacjaDiety.Drapieżnik,
111  wiek: 3,
112  waga: 350,
113  status: StatusZdrowia.Doskonały,
114  daneWitalne: {
115    temperatura: 39.1,
116    tętno: 95,
117    ciśnienieKrwi: [130, 85],
118    poziomStresu: 2
119  },
120  aktywnośćDziennaArray: [4, 2, 1, 0, 2, 5, 6, 3]
121});
122
123systemJP.dodajDinozaura({
124  id: 3,
125  nazwa: "Spike",
126  gatunek: "Stegosaurus",
127  klasyfikacjaDiety: KlasyfikacjaDiety.Roślinożerca,
128  wiek: 12,
129  waga: 5000,
130  status: StatusZdrowia.Wymagauwagi,
131  daneWitalne: {
132    temperatura: 37.5,
133    tętno: 50,
134    ciśnienieKrwi: [125, 80],
135    poziomStresu: 6
136  },
137  aktywnośćDziennaArray: [3, 3, 2, 1, 3, 2, 2, 1]
138});
139
140// Korzystanie z systemu
141const drapieżniki = systemJP.pobierzDrapieżniki();
142console.log(`Liczba drapieżników w parku: ${drapieżniki.length}`);
143
144const wymagającyUwagi = systemJP.pobierzDinozauryWymagająceUwagi();
145console.log(`Liczba dinozaurów wymagających uwagi: ${wymagającyUwagi.length}`);
146
147// Przykład raportu dziennego (tablica krotek)
148const raportDzienny = systemJP.przygotujRaportDzienny();
149console.log("\nDzienny raport stanu dinozaurów:");
150raportDzienny.forEach(([nazwa, id, status]) => {
151  console.log(`- ${nazwa} (ID: ${id}): ${status}`);
152});
153
154// Przykład aktualizacji statusu
155systemJP.aktualizujStatusZdrowia(1, StatusZdrowia.Wymagauwagi);

Podsumowanie typów złożonych

TypZastosowanie w Parku JurajskimPrzykład
ArrayKolekcje podobnych elementów, np. lista dinozaurówstring[], Dinozaur[]
Multidimensional ArrayReprezentacja przestrzenna, np. mapa parkunumber[][]
TupleStałe struktury z określonymi typami i rozmiarami, np. dane genetyczne[string, string, number, boolean]
EnumZdefiniowane kategorie, np. poziomy zagrożeniaenum KategoriaZagrożenia {...}
Const EnumZoptymalizowane enumy dla wydajnościconst enum StrefaBezpieczeństwa {...}

Pamiętaj, że dobór odpowiednich typów danych w projekcie jest jak projektowanie bezpiecznych i efektywnych wybiegów w Parku Jurajskim - dobre projektowanie na początku zaoszczędzi wielu problemów w przyszłości!

Dr. Wu zawsze powtarza: "Precyzyjna kontrola typów to podstawa sukcesu - zarówno w laboratorium genetycznym, jak i w kodzie TypeScript."

Kod do tej lekcji: index.ts
1// Typy złożone w Parku Jurajskim
2console.log("Typy złożone TypeScript\n");
3
4// ===========================================
5// 1. ARRAY - tablice
6// ===========================================
7console.log("=== TABLICE (ARRAY) ===");
8
9// Dwa sposoby definiowania tablic
10let dinosaurs: string[] = ["Tyrannosaurus", "Velociraptor", "Triceratops"];
11let weights: Array<number> = [7500, 350, 8500, 35000];
12
13console.log("Dinosaurs:", dinosaurs);
14console.log("First dinosaur:", dinosaurs[0]);
15console.log("Count:", dinosaurs.length);
16
17// Operacje na tablicach
18dinosaurs.push("Spinosaurus");
19console.log("After push:", dinosaurs);
20
21// Iteracja
22dinosaurs.forEach((dino, index) => {
23  console.log(`${index + 1}. ${dino}`);
24});
25
26// Tablica obiektów
27interface Dinosaur {
28  id: number;
29  name: string;
30  species: string;
31  isCarnivore: boolean;
32  ageMillionYears: number;
33}
34
35const collection: Dinosaur[] = [
36  { id: 1, name: "Rex", species: "Tyrannosaurus", isCarnivore: true, ageMillionYears: 68 },
37  { id: 2, name: "Blue", species: "Velociraptor", isCarnivore: true, ageMillionYears: 75 },
38  { id: 3, name: "Trike", species: "Triceratops", isCarnivore: false, ageMillionYears: 68 }
39];
40
41console.log("\nDinosaur collection:", collection);
42
43// Filtrowanie
44const carnivores = collection.filter(d => d.isCarnivore);
45console.log("Carnivores:", carnivores.map(d => d.name));
46
47// ===========================================
48// 2. TUPLE - krotki
49// ===========================================
50console.log("\n=== TUPLE - KROTKI ===");
51
52// Tuple - tablica o stałej długości i typach
53let dinoData: [string, number, boolean] = ["T-Rex", 8000, true];
54
55console.log("Species:", dinoData[0]);
56console.log("Weight:", dinoData[1]);
57console.log("Is dangerous:", dinoData[2]);
58
59// Tuple z nazwanymi elementami (TS 4.0+)
60type DinoTuple = [species: string, weight: number, dangerous: boolean];
61let velociraptor: DinoTuple = ["Velociraptor", 350, true];
62
63console.log("Velociraptor data:", velociraptor);
64
65// Tuple z opcjonalnymi elementami
66type PaddockData = [id: string, capacity: number, isLocked?: boolean];
67let paddock1: PaddockData = ["P-01", 5, true];
68let paddock2: PaddockData = ["P-02", 10]; // isLocked opcjonalne
69
70console.log("Paddock 1:", paddock1);
71console.log("Paddock 2:", paddock2);
72
73// ===========================================
74// 3. ENUM - wyliczenia
75// ===========================================
76console.log("\n=== ENUM - WYLICZENIA ===");
77
78// Enum numeryczny
79enum SecurityLevel {
80  Green = 1,
81  Yellow = 2,
82  Orange = 3,
83  Red = 4,
84  Critical = 5
85}
86
87let currentSecurity: SecurityLevel = SecurityLevel.Orange;
88console.log("Security level:", SecurityLevel[currentSecurity]); // "Orange"
89console.log("Security value:", currentSecurity); // 3
90
91// Enum tekstowy
92enum DinosaurDiet {
93  Carnivore = "CARNIVORE",
94  Herbivore = "HERBIVORE",
95  Omnivore = "OMNIVORE"
96}
97
98let trexDiet: DinosaurDiet = DinosaurDiet.Carnivore;
99console.log("T-Rex diet:", trexDiet); // "CARNIVORE"
100
101// Enum z obliczeniami
102enum PaddockSize {
103  Small = 100,
104  Medium = 500,
105  Large = 1000,
106  ExtraLarge = Large * 2 // 2000
107}
108
109console.log("Extra large paddock:", PaddockSize.ExtraLarge);
110
111// Const enum (optymalizacja)
112const enum DinosaurPeriod {
113  Triassic = "TRIASSIC",
114  Jurassic = "JURASSIC",
115  Cretaceous = "CRETACEOUS"
116}
117
118let trexPeriod: DinosaurPeriod = DinosaurPeriod.Cretaceous;
119console.log("T-Rex period:", trexPeriod);
120
121// ===========================================
122// 4. Funkcje pomocnicze dla typów złożonych
123// ===========================================
124console.log("\n=== OPERACJE NA TYPACH ZŁOŻONYCH ===");
125
126// Funkcja dla tablic
127function getTotalWeight(dinosaurs: Dinosaur[]): number {
128  return weights.reduce((sum, w) => sum + w, 0);
129}
130
131console.log("Total weight:", getTotalWeight(collection));
132
133// Funkcja dla tuple
134function createDinoTuple(species: string, weight: number, dangerous: boolean): DinoTuple {
135  return [species, weight, dangerous];
136}
137
138const newDino = createDinoTuple("Stegosaurus", 5000, false);
139console.log("New dinosaur:", newDino);
140
141// Funkcja dla enum
142function getSecurityColor(level: SecurityLevel): string {
143  switch (level) {
144    case SecurityLevel.Green:
145      return "Green";
146    case SecurityLevel.Yellow:
147      return "Yellow";
148    case SecurityLevel.Orange:
149      return "Orange";
150    case SecurityLevel.Red:
151      return "Red";
152    case SecurityLevel.Critical:
153      return "Critical";
154    default:
155      return "Unknown";
156  }
157}
158
159console.log("Current security:", getSecurityColor(currentSecurity));
160
161// ===========================================
162// 5. Zagnieżdżone struktury
163// ===========================================
164console.log("\n=== ZAGNIEŻDŻONE STRUKTURY ===");
165
166interface Paddock {
167  id: string;
168  size: PaddockSize;
169  security: SecurityLevel;
170  dinosaurs: Dinosaur[];
171  coordinates: [latitude: number, longitude: number];
172}
173
174const trexPaddock: Paddock = {
175  id: "P-01",
176  size: PaddockSize.Large,
177  security: SecurityLevel.Critical,
178  dinosaurs: [collection[0]],
179  coordinates: [22.3964, -159.5261]
180};
181
182console.log("T-Rex Paddock:", trexPaddock);
183console.log("Coordinates:", trexPaddock.coordinates);
184console.log("Dinosaurs in paddock:", trexPaddock.dinosaurs.length);
185
186// ===========================================
187// 6. Podsumowanie
188// ===========================================
189console.log("\n=== PODSUMOWANIE ===");
190console.log("Array - kolekcje elementów tego samego typu");
191console.log("Tuple - tablice o stałej długości i różnych typach");
192console.log("Enum - nazwane stałe wartości");
193console.log("Zagnieżdżanie - łączenie złożonych struktur");
194
195console.log("\nTypy złożone opanowane!");

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Czym jest tuple w TypeScript?

Zadania praktyczne w grze

  • Edytor kodu

    Zdefiniuj tablicę gatunków (string[]) i krotkę [string, number] dla nazwy i wagi dinozaura.

  • Układanie w pionie

    Uporządkuj typy TypeScript od najbardziej ogólnego do najbardziej szczegółowego:

  • Układanie w poziomie

    Ułóż elementy deklaracji tuple w TypeScript:

Przydatne artykuły