Instrukcja switch
Gdy jedna zmienna może przyjąć kilkanaście konkretnych wartości, drabinka
else if staje się ścianą tekstu. switch zapisuje to samo krócej —
a w połączeniu z typami literalnymi kompilator sam pilnuje, żebyś nie zapomniał o żadnym przypadku.
Czego się dziś nauczysz
- Zapiszesz wybór wielowariantowy instrukcją
switch - Wyjaśnisz, co się dzieje, gdy zabraknie
break - Połączysz kilka wartości w jeden przypadek
- Ocenisz, kiedy
switchjest lepszy od drabinkielse if - Sprawisz, że kompilator zgłosi błąd, gdy pominiesz jakąś wartość typu
Składnia
switch (wyrażenie) {
case wartość1:
// instrukcje
break;
case wartość2:
// instrukcje
break;
default:
// gdy nic nie pasowało
}
Program oblicza wyrażenie w nawiasie i porównuje je z kolejnymi etykietami case
— dokładnie tak, jakby użył operatora ===. Przy pierwszym trafieniu zaczyna wykonywać
kod, a break wyprowadza go z całej instrukcji.
let ocena: number = 4;
switch (ocena) {
case 6:
console.log("celujący");
break;
case 5:
console.log("bardzo dobry");
break;
case 4:
console.log("dobry");
break;
case 3:
console.log("dostateczny");
break;
case 2:
console.log("dopuszczający");
break;
case 1:
console.log("niedostateczny");
break;
default:
console.log("nieprawidłowa ocena");
}
dobry
To samo w drabince else if zajęłoby tyle samo linii, ale każda z nich powtarzałaby
nazwę zmiennej i operator porównania. switch mówi raz: „porównuję tę wartość”,
a dalej wypisuje już same możliwości.
Blok default jest opcjonalny
Jeśli go pominiesz i nic nie pasuje, switch po prostu nic nie zrobi. Warto go jednak
dopisywać — przechwytuje wartości, o których nie pomyślałeś, i jest dobrym miejscem
na komunikat o błędzie.
Dlaczego dane są podane w ten sposób?
Przy const ocena = 4 kompilator zna dokładnie wartość 4 i może odrzucić pozostałe case jako niemożliwe. Zmienna liczbowa let oraz funkcje daneWejsciowe() symulują wynik pochodzący z zewnątrz i zachowują szerszy typ. Funkcje poznasz dokładnie w lekcji 11; tutaj funkcja po prostu zwraca wskazaną wartość.
Przy rozpoznawaniu pojedynczej cyfry użyj porównania znaków od „0” do „9”, zamiast Number, które akceptuje też białe znaki.
Rola instrukcji break
switch nie kończy się po wykonaniu pasującego przypadku — wykonuje wszystko,
co jest poniżej, aż napotka break. Bez niego dostajemy tak zwane
przelatywanie (ang. fall-through):
let ocena: number = 5;
switch (ocena) {
case 6:
console.log("celujący");
case 5:
console.log("bardzo dobry");
case 4:
console.log("dobry");
default:
console.log("koniec");
}
bardzo dobry
dobry
koniec
Najczęstszy błąd przy switch
Program wszedł w case 5 i poleciał dalej przez wszystkie kolejne bloki.
Włącz w tsconfig.json opcję "noFallthroughCasesInSwitch": true,
a kompilator zacznie zgłaszać takie sytuacje jako błąd.
Wyjątek: gdy blok kończy się instrukcją return (w funkcji, lekcja 11),
break jest zbędny — return i tak opuszcza całą funkcję.
Grupowanie przypadków
Przelatywanie bywa jednak przydatne — świadomie użyte pozwala obsłużyć kilka wartości
tym samym kodem. Etykiety case stawia się wtedy jedna pod drugą, bez instrukcji
pomiędzy nimi:
type Dzien = "pon" | "wt" | "sr" | "czw" | "pt" | "sob" | "ndz";
function daneWejsciowe(): Dzien { return "sob"; }
const dzien = daneWejsciowe();
switch (dzien) {
case "sob":
case "ndz":
console.log("Weekend — szkoła zamknięta");
break;
case "pon":
case "wt":
case "sr":
case "czw":
console.log("Dzień nauki, zajęcia do 15:20");
break;
case "pt":
console.log("Dzień nauki, zajęcia do 13:30");
break;
}
Drugi typowy przykład — liczba dni w miesiącu:
let miesiac: number = 2;
const rok = 2026;
let dni: number;
switch (miesiac) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
dni = 31;
break;
case 4: case 6: case 9: case 11:
dni = 30;
break;
case 2:
dni = (rok % 4 === 0 && rok % 100 !== 0) || rok % 400 === 0 ? 29 : 28;
break;
default:
dni = 0;
console.log("Nieprawidłowy numer miesiąca");
}
console.log(`Miesiąc ${miesiac}/${rok} ma ${dni} dni`);
switch czy if?
| Sytuacja | Wybierz |
|---|---|
| Jedna zmienna, kilka konkretnych wartości (status, kod, litera, dzień) | switch |
Przedziały liczbowe (>= 90, >= 75…) | if / else if |
Warunki złożone z && i || na wielu zmiennych | if |
| Sprawdzenie pojedynczego warunku logicznego | if |
Zmienna typu unii literałów albo enum | switch |
switch nie obsługuje przedziałów
Zapis case procent >= 90: nie zadziała tak, jak myślisz — po case
musi stać wartość do porównania przez ===, a nie warunek. Do progów używaj
drabinki else if z poprzedniej lekcji.
Kontrola kompletności
Tu switch pokazuje przewagę, której zwykły JavaScript nie ma. Gdy zmienna jest
typu unii literałów, kompilator wie, ile możliwości istnieje — i potrafi sprawdzić,
czy obsłużyłeś wszystkie:
type Status = "nowe" | "wydane" | "anulowane";
function daneWejsciowe(): Status { return "wydane"; }
const status = daneWejsciowe();
let opis: string;
switch (status) {
case "nowe":
opis = "Oczekuje na realizację";
break;
case "wydane":
opis = "Zamówienie odebrane";
break;
case "anulowane":
opis = "Zamówienie anulowane";
break;
}
console.log(opis); // OK — kompilator widzi, że każda możliwość ma przypisanie
Usuń teraz ostatni przypadek i skompiluj ponownie. Dostaniesz:
error TS2454: Variable 'opis' is used before being assigned.
Kompilator wykrył, że istnieje wartość ("anulowane"), dla której zmienna
nie dostaje nic. To ogromna pomoc przy rozbudowie programu: gdy dopiszesz do typu
Status nową wartość, kompilator sam wskaże wszystkie miejsca w projekcie,
które trzeba uzupełnić.
Wzorzec z never
Gdy w switch nie przypisujesz zmiennej, tylko wykonujesz działania, powyższa
kontrola nie zadziała. Stosuje się wtedy sztuczkę ze specjalnym typem never,
który oznacza „wartość, która nie może wystąpić”:
type Status = "nowe" | "wydane" | "anulowane";
function daneWejsciowe(): Status { return "nowe"; }
const status = daneWejsciowe();
switch (status) {
case "nowe":
console.log("Przygotuj zamówienie");
break;
case "wydane":
console.log("Zamknij zamówienie");
break;
case "anulowane":
console.log("Zwróć towar do magazynu");
break;
default:
// jeśli obsłużyliśmy wszystko, tutaj typ status to "never"
const niemozliwe: never = status;
console.log(`Nieobsłużony status: ${niemozliwe}`);
}
Po co to komu
Dopisz do typu Status czwartą wartość, na przykład "opoznione".
Linia z never natychmiast zapali się na czerwono — bo do default
może teraz trafić realna wartość. Kompilator przypomina ci o niedokończonej robocie.
Typ never omówimy dokładniej w lekcji 17.
Kalkulator konsolowy
Klasyczne zadanie egzaminacyjne: dwie liczby, znak działania, wynik. Program obsługuje też przypadki brzegowe i konwersję jednostek.
type Dzialanie = "+" | "-" | "*" | "/" | "%" | "^";
// ── dane wejściowe ──
const a = 17;
let b: number = 5;
function daneWejsciowe(): Dzialanie { return "/"; }
const operacja = daneWejsciowe();
// ── obliczenie ──
let wynik: number;
let opis: string;
let blad = false;
switch (operacja) {
case "+":
wynik = a + b;
opis = "suma";
break;
case "-":
wynik = a - b;
opis = "różnica";
break;
case "*":
wynik = a * b;
opis = "iloczyn";
break;
case "/":
if (b === 0) {
wynik = 0;
opis = "dzielenie przez zero";
blad = true;
} else {
wynik = a / b;
opis = "iloraz";
}
break;
case "%":
if (b === 0) {
wynik = 0;
opis = "reszta z dzielenia przez zero";
blad = true;
} else {
wynik = a % b;
opis = "reszta z dzielenia";
}
break;
case "^":
wynik = a ** b;
opis = "potęga";
break;
}
// ── wypisanie ──
console.log("═".repeat(38));
if (blad) {
console.log(`BŁĄD: ${opis}`);
} else {
console.log(`${a} ${operacja} ${b} = ${wynik}`);
console.log(`Działanie: ${opis}`);
// dodatkowe informacje o wyniku
switch (true) {
case wynik < 0:
console.log("Wynik ujemny");
break;
case wynik === 0:
console.log("Wynik zerowy");
break;
default:
console.log("Wynik dodatni");
}
console.log(`Zaokrąglony: ${Math.round(wynik)}`);
console.log(`Całkowity: ${Number.isInteger(wynik) ? "tak" : "nie"}`);
}
console.log("═".repeat(38));
══════════════════════════════════════
17 / 5 = 3.4
Działanie: iloraz
Wynik dodatni
Zaokrąglony: 3
Całkowity: nie
══════════════════════════════════════
Omówienie
| Fragment | Dlaczego tak |
|---|---|
type Dzialanie = "+" | "-" | … |
Dzięki unii literałów kompilator zna wszystkie sześć możliwości i pilnuje, żeby każda
miała swój case. Pominięcie jednego to natychmiastowy TS2454
przy wynik. |
if wewnątrz case "/" |
W bloku case może stać dowolny kod, także instrukcja warunkowa.
Tu sprawdzamy dzielnik, zanim wykonamy dzielenie. |
switch (true) |
Sztuczka: skoro po case stoi wartość porównywana przez ===,
to porównując z true możemy w etykietach umieścić warunki. Działa, ale używaj
oszczędnie — zwykły if / else if jest tu czytelniejszy. |
Number.isInteger(wynik) |
Sprawdzenie, czy liczba jest całkowita. Przydaje się, gdy wynik dzielenia trzeba wyświetlić inaczej niż wynik dodawania. |
Sprawdź to sam
Zmień b na 0 i operacja na "/" — zobaczysz
komunikat błędu zamiast Infinity. Potem dopisz do typu Dzialanie
wartość "//" (dzielenie całkowite) i sprawdź, jak kompilator upomni się
o brakujący przypadek.
Na co uważać
| Kod | Problem |
|---|---|
brak break |
Program wykona też kolejne przypadki. Włącz noFallthroughCasesInSwitch. |
case procent >= 90: |
Po case musi być wartość, nie warunek. Do przedziałów użyj
else if. |
case "5": przy zmiennej liczbowej |
Porównanie działa jak ===, więc tekst nigdy nie zrówna się z liczbą. |
const x = …; w dwóch blokach case |
Wszystkie case dzielą jeden zasięg, więc powtórzona nazwa to błąd.
Otocz zawartość przypadku własnymi klamrami: case 1: { … break; } |
switch na typie boolean |
Dwie możliwości to zadanie dla if / else, nie dla switch. |
brak default przy danych z zewnątrz |
Wartość spoza listy przejdzie bez śladu. Dopisz default z komunikatem. |
Dwie rzeczy z przyszłości
W przykładach tej lekcji celowo używamy dwóch elementów, które poznasz później. function daneWejsciowe(): Dzien { … } to funkcja udająca dane od użytkownika — funkcjami zajmiemy się w lekcji 11. Zapis type Dzien = "pon" | "wt" | … to unia literałów, czyli typ dopuszczający tylko wymienione wartości; pełne omówienie w lekcji 15. Tutaj wystarczy je czytać, nie musisz ich samodzielnie projektować.
Zadania
let x: number = 2;
switch (x) {
case 1:
console.log("jeden");
break;
case 2:
console.log("dwa");
case 3:
console.log("trzy");
break;
case 4:
console.log("cztery");
break;
default:
console.log("inne");
}
pokaż rozwiązanie
dwa
trzy
Po case 2 brakuje break, więc program przelatuje do
case 3 i dopiero tam się zatrzymuje. Do case 4 i
default już nie dotrze.
Napisz program, który zamienia ocenę słowną (typ literalny: "celujący",
"bardzo dobry", …) na cyfrę i odwrotnie — dwie osobne instrukcje
switch. Sprawdź, czy kompilator upomni się o brakujący przypadek, gdy usuniesz
jedną z gałęzi.
Zasymuluj menu automatu z napojami. Zmienna wybor typu
"1" | "2" | "3" | "4" | "0" odpowiada pozycjom: kawa 4 zł, herbata 3 zł,
czekolada 5 zł, woda 2 zł, anulowanie. Dla każdej pozycji wypisz nazwę i cenę, a dla
anulowania — komunikat o zwrocie monet. Dodaj sprawdzenie, czy wrzucona kwota wystarcza,
i wypisz resztę.
pokaż przykładowe rozwiązanie
type Wybor = "1" | "2" | "3" | "4" | "0";
function daneWejsciowe(): Wybor { return "3"; }
const wybor = daneWejsciowe();
const wrzucone = 4.5;
let nazwa: string;
let cena: number;
switch (wybor) {
case "1": nazwa = "kawa"; cena = 4; break;
case "2": nazwa = "herbata"; cena = 3; break;
case "3": nazwa = "czekolada"; cena = 5; break;
case "4": nazwa = "woda"; cena = 2; break;
case "0": nazwa = "anulowano"; cena = 0; break;
}
if (wybor === "0") {
console.log(`Anulowano. Zwrot: ${wrzucone.toFixed(2)} zł`);
} else if (wrzucone < cena) {
console.log(`${nazwa}: ${cena.toFixed(2)} zł`);
console.log(`Brakuje ${(cena - wrzucone).toFixed(2)} zł`);
} else {
console.log(`Wydaję: ${nazwa}`);
console.log(`Reszta: ${(wrzucone - cena).toFixed(2)} zł`);
}
Mając pojedynczy znak w zmiennej, ustal jego kategorię: samogłoska, spółgłoska, cyfra,
znak interpunkcyjny (. , ! ?) lub inny. Użyj grupowania przypadków —
wszystkie samogłoski w jednej grupie case. Pamiętaj o wielkich literach.
pokaż wskazówki
Sprowadź znak do małej litery przez const z = znak.toLowerCase();. Grupuj samogłoski w case (uwzględnij też ą, ę, ó dla polskich liter). Cyfrę ASCII sprawdź warunkiem z >= "0" && z <= "9".
Napisz program wydający resztę monetami. Dane: kwota do wydania w groszach.
Wypisz, ile sztuk każdego nominału trzeba wydać (5 zł, 2 zł, 1 zł, 50 gr, 20 gr, 10 gr,
5 gr, 2 gr, 1 gr), pomijając nominały o zerowej liczbie. Dodatkowo, na podstawie typu
wyliczeniowego Kasa (Pelna, BrakDrobnych,
Pusta), instrukcją switch wypisz komunikat o możliwości
wydania reszty.
pokaż wskazówki
Nominały umieść w tablicy const nominaly = [500, 200, 100, 50, 20, 10, 5, 2, 1];
i przejdź ją pętlą for…of. Dla każdego licz
Math.floor(reszta / nominal), a potem reszta = reszta % nominal;.
To ten sam wzorzec, którego użyliśmy przy rozbijaniu czasu w lekcji 4 — tylko powtórzony
dziewięć razy zamiast dwóch.
Co trzeba zapamiętać
switchporównuje wartość operatorem===— nie obsługuje przedziałów.- Bez
breakprogram wykonuje także kolejne przypadki. - Etykiety
casejedna pod drugą grupują wartości obsługiwane tym samym kodem. defaultłapie wszystko, co nie pasowało — warto go dopisywać.- Sama unia nie wymusza pełnego switch. Kontrolę kompletności uzyskasz przez użycie zmiennej wymagającej przypisania w każdej gałęzi, jawny typ wyniku funkcji albo wzorzec never.
- Progi liczbowe zostają domeną drabinki
else if.