Krotki i typy wyliczeniowe
Tablica number[] przechowuje liczby, ale typ tablicy może też dopuszczać różne rodzaje wartości. Krotka dodatkowo opisuje pozycje, np. nazwę produktu na początku i cenę na drugim miejscu.
Czego się dziś nauczysz
- Zadeklarujesz krotkę o ustalonej długości i kolejności typów
- Rozpakujesz krotkę i tablicę do osobnych zmiennych jedną linijką
- Utworzysz typ wyliczeniowy i użyjesz go zamiast „magicznych” liczb
- Zapiszesz zamknięty zestaw wartości jako unię typów literalnych
- Wybierzesz świadomie między
enuma unią literałów
Krotka — tablica o ustalonym składzie
Krotka (ang. tuple) wygląda jak tablica, ale kompilator pilnuje w niej dwóch dodatkowych rzeczy: ile jest elementów i jakiego typu jest każdy z nich po kolei.
// zwykła tablica — dowolna długość, jeden typ
const liczby: number[] = [1, 2, 3, 4];
// krotka — dokładnie dwa elementy, w tej kolejności
const produkt: [string, number] = ["drożdżówka", 3.5];
console.log(produkt[0].toUpperCase()); // OK — element 0 jest tekstem
console.log(produkt[1].toFixed(2)); // OK — element 1 jest liczbą
const zly: [string, number] = [3.5, "drożdżówka"]; // Błąd TS2322 — zła kolejność
const krotki: [string, number] = ["sok"]; // Błąd TS2322 — brakuje elementu
Zwróć uwagę na pierwszą korzyść: po produkt[0]. edytor podpowie metody tekstu,
a po produkt[1]. metody liczby. W zwykłej tablicy mieszanego typu byłoby to niemożliwe.
Elementy nazwane
Same numery szybko przestają cokolwiek mówić. Elementom krotki można nadać nazwy — służą wyłącznie do czytania kodu i podpowiedzi w edytorze:
type Wspolrzedne = [szerokosc: number, dlugosc: number];
type WpisDziennika = [przedmiot: string, ocena: number, waga: number];
const lebork: Wspolrzedne = [54.5388, 17.7500];
const wpis: WpisDziennika = ["INF.04", 5, 3];
console.log(`Ocena ${wpis[1]} z ${wpis[0]}, waga ${wpis[2]}`);
Element opcjonalny i reszta
// trzeci element nie jest wymagany
type Adres = [miasto: string, ulica: string, mieszkanie?: string];
const a1: Adres = ["Lębork", "Marcinkowskiego 1"];
const a2: Adres = ["Lębork", "Marcinkowskiego 1", "12A"];
// pierwszy element ustalony, dalej dowolnie wiele liczb
type Pomiar = [nazwa: string, ...wartosci: number[]];
const temperatury: Pomiar = ["czujnik-1", 21.5, 22.0, 21.8];
Krotka to wciąż tablica
push działa na krotce i potrafi zepsuć jej długość — kompilator w niektórych
sytuacjach tego nie zauważy. Jeśli krotka ma naprawdę pozostać niezmienna, zapisz ją jako
readonly [string, number]. Wtedy push i podmiana elementu są zabronione.
Rozpakowanie do zmiennych
Odwoływanie się do produkt[0] i produkt[1] jest nieczytelne.
Destrukturyzacja rozpakowuje krotkę do nazwanych zmiennych jedną linijką:
const produkt: [string, number] = ["drożdżówka", 3.5];
// zamiast tego...
const nazwa1 = produkt[0];
const cena1 = produkt[1];
// ...piszemy to
const [nazwa, cena] = produkt;
console.log(`${nazwa} kosztuje ${cena.toFixed(2)} zł`);
Ten sam zapis działa na zwykłych tablicach i przydaje się przy rozbijaniu tekstu:
const wiersz = "Kowalska;Marta;3TI;4.36";
const [nazwisko, imie, klasa, srednia] = wiersz.split(";");
console.log(`${imie} ${nazwisko}, klasa ${klasa}, średnia ${srednia}`);
// pomijanie elementów przecinkiem
const [, , , tylkoSrednia] = wiersz.split(";");
console.log(tylkoSrednia); // 4.36
// reszta do osobnej tablicy
const oceny = [5, 4, 3, 5, 4];
const [pierwsza, ...pozostale] = oceny;
console.log(pierwsza); // 5
console.log(pozostale); // [4, 3, 5, 4]
// zamiana wartości miejscami — bez zmiennej pomocniczej
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(`${a} ${b}`); // 2 1
Gdzie to spotkasz
Destrukturyzacja tablic jest fundamentem Reacta (const [stan, ustawStan] = useState(0))
i bardzo częstym zapisem w Angularze. Warto ją oswoić teraz, na prostych przykładach.
Typ wyliczeniowy
Wyobraź sobie status zamówienia w sklepiku. Bez typu wyliczeniowego kod wygląda tak:
let status = 2; // co znaczy 2? nikt nie pamięta
if (status === 3) { } // a co znaczy 3?
Liczby bez znaczenia (magic numbers) to prosta droga do błędu. enum nadaje im nazwy:
enum Status {
Nowe, // 0 — numeracja nadawana automatycznie
Przyjete, // 1
Wydane, // 2
Anulowane // 3
}
let status: Status = Status.Przyjete;
console.log(status); // 1 — pod spodem to nadal liczba
console.log(Status.Wydane); // 2
console.log(Status[2]); // "Wydane" — odczyt nazwy po numerze
status = Status.Wydane; // OK
status = 7; // Błąd TS2322 — 7 nie należy do Status
Enum tekstowy
Numery są niewygodne, gdy wartość trafia do bazy danych, pliku albo JSON-a — w logach widzisz wtedy tajemnicze cyfry. Dlatego znacznie częściej stosuje się wariant tekstowy:
enum Poziom {
Podstawowy = "PODSTAWOWY",
Sredni = "SREDNI",
Zaawansowany = "ZAAWANSOWANY"
}
const kurs: Poziom = Poziom.Sredni;
console.log(kurs); // "SREDNI" — czytelne w logu i w bazie
Własne numery
enum KodHttp {
OK = 200,
Przekierowanie = 301,
BrakDostepu = 403,
NieZnaleziono = 404,
BladSerwera = 500
}
const odpowiedz = KodHttp.NieZnaleziono;
console.log(`Serwer zwrócił ${odpowiedz} (${KodHttp[odpowiedz]})`);
// Serwer zwrócił 404 (NieZnaleziono)
Enum zostaje po kompilacji
Zwykły enum wytwarza obiekt w JavaScripcie. Dlatego dla enuma liczbowego Status[2] może odczytać nazwę. Alias typu i sam opis krotki znikają, ale dane zapisane w tablicach nadal istnieją.
Unia typów literalnych
Jest jeszcze prostszy sposób na zamknięty zestaw wartości. Typem może być konkretna
wartość, a znak | oznacza „albo”:
type Status = "nowe" | "przyjete" | "wydane" | "anulowane";
type Ocena = 1 | 2 | 3 | 4 | 5 | 6;
type Kierunek = "gora" | "dol" | "lewo" | "prawo";
let status: Status = "przyjete";
status = "wydane"; // OK
status = "gotowe"; // Błąd TS2322 — nie ma takiej wartości w typie
const ocena: Ocena = 5; // OK
const zla: Ocena = 7; // Błąd — siódemki nie ma na liście
Unia literałów ogranicza zestaw dopuszczalnych wartości bez tworzenia dodatkowego obiektu w runtime. To często prosty wybór dla tekstowych statusów. Nie wszystkie reguły zgodności enumów i unii są identyczne.
type Status = "nowe" | "przyjete" | "wydane" | "anulowane";
const status: Status = "wydane";
console.log(status.toUpperCase()); // WYDANE — to nadal string
console.log(`Zamówienie: ${status}`); // bez żadnej konwersji
Enum czy unia literałów?
| Kryterium | enum | unia literałów |
|---|---|---|
| Zapis | osobna deklaracja, kilka linii | jedna linia |
| Po kompilacji | zostaje obiekt w pliku .js | znika bez śladu |
| Odczyt nazwy z wartości | tak, Status[2] | nie ma takiej potrzeby — wartość jest nazwą |
| Użycie w kodzie | Status.Wydane | "wydane" |
| Dane z JSON-a / API | wymaga mapowania | pasuje bezpośrednio |
| Iteracja po wszystkich wartościach | łatwa (Object.values) | trzeba dopisać tablicę |
Praktyczna wskazówka
Zacznij od unii literałów — jest krótsza i nie zostawia śladu w wynikowym kodzie.
Po enum sięgnij, gdy naprawdę potrzebujesz numerów (np. kody HTTP, wartości z bazy)
albo listy wszystkich możliwości do przejścia w pętli. W kursie stosujemy oba, żebyś rozpoznawał
je w cudzym kodzie.
Panel zamówień w sklepiku
Program przechowuje zamówienia jako krotki, przelicza wartość i wypisuje raport pogrupowany po statusie. Wszystkie trzy konstrukcje z tej lekcji pracują razem.
// ── typy ──
type Status = "nowe" | "wydane" | "anulowane";
type Zamowienie = [numer: number, produkt: string, sztuk: number, cena: number, status: Status];
enum Priorytet {
Zwykly = 1,
Pilny = 2,
Natychmiastowy = 3
}
// ── dane ──
const zamowienia: Zamowienie[] = [
[101, "drożdżówka", 3, 3.5, "wydane"],
[102, "sok", 2, 2.8, "nowe"],
[103, "kanapka", 1, 6.0, "anulowane"],
[104, "drożdżówka", 5, 3.5, "nowe"],
[105, "woda", 4, 2.0, "wydane"]
];
// ── obliczenia ──
let wartoscWydanych = 0;
let liczbaNowych = 0;
let liczbaAnulowanych = 0;
console.log("NR PRODUKT SZT WARTOŚĆ STATUS");
console.log("─".repeat(48));
for (const zam of zamowienia) {
const [numer, produkt, sztuk, cena, status] = zam;
const wartosc = sztuk * cena;
console.log(
String(numer).padEnd(5) +
produkt.padEnd(14) +
String(sztuk).padStart(3) +
(wartosc.toFixed(2) + " zł").padStart(10) + " " +
status
);
if (status === "wydane") wartoscWydanych += wartosc;
if (status === "nowe") liczbaNowych++;
if (status === "anulowane") liczbaAnulowanych++;
}
// ── priorytet obsługi zależny od liczby oczekujących ──
const priorytet: Priorytet =
liczbaNowych >= 5 ? Priorytet.Natychmiastowy
: liczbaNowych >= 2 ? Priorytet.Pilny
: Priorytet.Zwykly;
console.log("─".repeat(48));
console.log(`Utarg z wydanych: ${wartoscWydanych.toFixed(2)} zł`);
console.log(`Oczekujące: ${liczbaNowych}`);
console.log(`Anulowane: ${liczbaAnulowanych}`);
console.log(`Priorytet obsługi: ${Priorytet[priorytet]} (${priorytet}/3)`);
NR PRODUKT SZT WARTOŚĆ STATUS
────────────────────────────────────────────────
101 drożdżówka 3 10.50 zł wydane
102 sok 2 5.60 zł nowe
103 kanapka 1 6.00 zł anulowane
104 drożdżówka 5 17.50 zł nowe
105 woda 4 8.00 zł wydane
────────────────────────────────────────────────
Utarg z wydanych: 18.50 zł
Oczekujące: 2
Anulowane: 1
Priorytet obsługi: Pilny (2/3)
Omówienie
| Fragment | Dlaczego tak |
|---|---|
type Zamowienie = [numer: number, …] |
Nazwane elementy krotki. Edytor przy wpisywaniu nowego zamówienia podpowie, że trzecia pozycja to liczba sztuk — bez zaglądania do dokumentacji. |
Zamowienie[] |
Tablica krotek: dowolnie wiele zamówień, każde o ściśle określonym składzie. To bardzo częsty sposób opisu danych tabelarycznych. |
const [numer, produkt, …] = zam; |
Rozpakowanie na początku pętli. Dalej w kodzie nie ma już żadnego zam[3],
tylko czytelne nazwy. |
status === "wydane" |
Zwykłe porównanie tekstów — a mimo to literówka "wydanee" zostanie zgłoszona
jako błąd, bo taka wartość nie należy do typu Status. |
Priorytet[priorytet] |
Odczyt nazwy z numeru — możliwe tylko dzięki temu, że enum zostaje w wynikowym kodzie. Unia literałów tego nie potrafi, bo nie musi. |
Kiedy krotka to za mało
Pięcioelementowa krotka jest na granicy czytelności. Przy większej liczbie pól albo gdy część
danych bywa nieobecna, lepszym rozwiązaniem jest obiekt z nazwanymi polami —
{ numer: 101, produkt: "sok", … }. Tym zajmiemy się w lekcji 14.
Na co uważać
| Kod | Problem |
|---|---|
const p: [string, number] = [3.5, "sok"]; |
Kolejność typów w krotce jest wiążąca. TS2322. |
const [a, b] = ["x"]; |
Przy zwykłej tablicy b będzie undefined i nikt nie ostrzeże.
Przy krotce kompilator wyłapie brak elementu. |
status = "Wydane"; |
Typy literalne rozróżniają wielkość liter. W typie jest "wydane". |
enum Status { Nowe } + if (status) |
Pierwsza wartość enuma to 0, czyli fałsz. Warunek nie zadziała.
Porównuj wprost: if (status === Status.Nowe). |
let s: Status = "nowe"; a potem s += "!" |
Wynik jest zwykłym string, więc nie zmieści się z powrotem w Status. |
Krotka rosnąca przez push |
Formalnie dozwolone, faktycznie psuje sens krotki. Dodaj readonly. |
Zadania
enum Dzien { Pon = 1, Wt, Sr, Czw, Pt }
console.log(Dzien.Pon);
console.log(Dzien.Czw);
console.log(Dzien[3]);
const dane = "5;4;3";
const [a, b, c] = dane.split(";");
console.log(b);
console.log(Number(a) + Number(c));
pokaż rozwiązanie
1
4 numeracja liczy się dalej od podanej wartości: Wt=2, Sr=3, Czw=4
Sr
4
8 "5" i "3" zamienione na liczby
Zadeklaruj typ Punkt jako krotkę z nazwanymi elementami (nazwa, szerokość, długość
geograficzna). Utwórz tablicę trzech punktów i wypisz je w pętli, rozpakowując każdy przez
destrukturyzację. Współrzędne wyświetl z czterema miejscami po przecinku.
pokaż rozwiązanie
type Punkt = [nazwa: string, szerokosc: number, dlugosc: number];
const miejsca: Punkt[] = [
["ZSMI Lębork", 54.5388, 17.7500],
["Gdańsk", 54.3520, 18.6466],
["Nowa Wieś Lęborska", 54.5667, 17.7333]
];
for (const punkt of miejsca) {
const [nazwa, szer, dlug] = punkt;
console.log(`${nazwa.padEnd(22)} ${szer.toFixed(4)}N ${dlug.toFixed(4)}E`);
}
Zapisz na dwa sposoby zestaw stanów sprzętu w wypożyczalni: dostępny, wypożyczony,
w naprawie, wycofany — raz jako enum tekstowy, raz jako unię literałów.
Dla każdego wariantu napisz zmienną, przypisanie i wypisanie wartości. Zapisz w komentarzu,
który wariant wybrałbyś do systemu współpracującego z bazą danych i dlaczego.
pokaż przykładowe rozwiązanie
enum StanEnum {
Dostepny = "DOSTEPNY",
Wypozyczony = "WYPOZYCZONY",
WNaprawie = "W_NAPRAWIE",
Wycofany = "WYCOFANY"
}
type StanUnia = "dostepny" | "wypozyczony" | "w_naprawie" | "wycofany";
const laptop: StanEnum = StanEnum.Wypozyczony;
const projektor: StanUnia = "w_naprawie";
console.log(`Laptop: ${laptop}`);
console.log(`Projektor: ${projektor}`);
// Do bazy danych wygodniejszy jest enum tekstowy: nazwy stałych są zebrane
// w jednym miejscu, a w kodzie nigdzie nie wpisuje się wartości "ręcznie".
Mecz zapisz jako krotkę [gospodarz: string, gosc: string, bramkiG: number, bramkiS: number].
Utwórz tablicę pięciu meczów i wypisz każdy w formacie Ala 3 : 1 Bartek — wygrana gospodarzy.
Wynik rozstrzygnięcia (wygrana gospodarzy / remis / wygrana gości)
zapisz jako unię typów literalnych. Na końcu podaj liczbę remisów i łączną liczbę bramek.
pokaż wskazówki
Typ wyniku: type Rozstrzygniecie = "gospodarze" | "remis" | "goscie";.
Wartość dobierz zagnieżdżonym operatorem warunkowym albo — po lekcji 06 — instrukcją
if / else if / else. Bramki sumuj w jednej zmiennej wewnątrz pętli.
Zbuduj plan jednego dnia: enum Dzien z numeracją od 1, unia literałów
Sala ("101" | "102" | "sala gimnastyczna") oraz krotka
Lekcja zawierająca numer godziny, przedmiot, salę i informację, czy lekcja jest
zdalna. Wypisz plan w tabelce, a na końcu policz, ile lekcji odbywa się stacjonarnie
i w której sali jest ich najwięcej.
pokaż przykładowe rozwiązanie
enum Dzien { Pon = 1, Wt, Sr, Czw, Pt }
type Sala = "101" | "102" | "sala gimnastyczna";
type Lekcja = [godzina: number, przedmiot: string, sala: Sala, zdalna: boolean];
const dzisiaj: Dzien = Dzien.Sr;
const plan: Lekcja[] = [
[1, "matematyka", "101", false],
[2, "INF.04", "102", false],
[3, "INF.04", "102", false],
[4, "wf", "sala gimnastyczna", false],
[5, "angielski", "101", true]
];
console.log(`PLAN — ${Dzien[dzisiaj]}`);
console.log("─".repeat(46));
let stacjonarne = 0;
let w101 = 0;
let w102 = 0;
for (const lekcja of plan) {
const [godzina, przedmiot, sala, zdalna] = lekcja;
const tryb = zdalna ? "zdalnie" : sala;
console.log(`${godzina}. ${przedmiot.padEnd(14)} ${tryb}`);
if (!zdalna) {
stacjonarne++;
if (sala === "101") w101++;
if (sala === "102") w102++;
}
}
console.log("─".repeat(46));
console.log(`Stacjonarnie: ${stacjonarne} z ${plan.length}`);
console.log(`Najczęstsza sala: ${w101 >= w102 ? "101" : "102"}`);
Zliczanie osobnymi zmiennymi robi się uciążliwe przy większej liczbie sal. Wygodniejsze
narzędzie — słownik Map — poznasz w lekcji 24.
Co trzeba zapamiętać
- Krotka opisuje pozycje i ich typy. Może mieć elementy opcjonalne i rest, więc nie każda krotka ma jedną stałą długość. Nie jest kontrolą długości w runtime.
- Elementy krotki warto nazywać — nazwy trafiają do podpowiedzi edytora.
- Destrukturyzacja
const [a, b] = krotka;rozpakowuje wartości do zmiennych; działa też na zwykłych tablicach i na wynikusplit. - Zwykły enum tworzy również obiekt w wynikowym JavaScripcie. Typy i interfejsy znikają. Enum nie jest jedyną konstrukcją pozostającą w JS — klasy i funkcje także działają po kompilacji.
- Unia literałów, np.
"nowe" | "wydane", opisuje zamknięty zestaw tekstów i znika po kompilacji. - Domyślnie wybieraj unię literałów; enum tam, gdzie potrzebne są numery lub lista wszystkich wartości.