Unie i zawężanie typu

Operacja może się udać albo zakończyć błędem. Opisz oba warianty, zamiast tworzyć obiekt z przypadkowym zestawem pustych pól.

CEL LEKCJI

Po tej lekcji potrafisz

  • Opiszesz wybór między typami przez unię.
  • Zawęzisz typ przez typeof, in i wyróżnik.
  • Zbudujesz własny strażnik typu i zrozumiesz odpowiedzialność za jego wynik.
WPROWADZENIE

Czy już mamy wynik meczu?

Przed meczem nie ma zwycięzcy. Po meczu jest wynik, a po odwołaniu — powód. Typ z samymi opcjonalnymi polami pozwoliłby pomieszać te stany. Unia umożliwia zapisanie kilku konkretnych wariantów.

POZNAJ SKŁADNIĘ

Małe kroki, konkretne efekty

Pionowa kreska daje wybór

number | string oznacza jedną wartość, która może być liczbą lub tekstem. Nie możesz bez sprawdzenia używać metod dostępnych tylko dla jednego wariantu.

src/index.ts — osobny przykład
function opisId(id: number | string): string {
  if (typeof id === "number") { return "Numer " + id.toFixed(0); }
  return "Kod " + id.toUpperCase();
}
console.log(opisId(12));
console.log(opisId("ab"));
wynik w konsoli
Numer 12
Kod AB

W gałęzi if kompilator wie, że id jest liczbą. Po return zostaje drugi wariant. To zawężanie: nie zmieniamy wartości, tylko ustalamy, który typ pasuje w tym miejscu.

Wyróżnik pozwala powiązać dane

Wspólne pole z różnymi stałymi tekstowymi nazywamy wyróżnikiem. Porównanie statusu podpowiada kompilatorowi, jakie pozostałe pola są dostępne.

src/index.ts — osobny przykład
type Odpowiedz =
  | { status: "ok"; punkty: number }
  | { status: "blad"; powod: string };
function pokaz(o: Odpowiedz): string {
  if (o.status === "ok") { return "Punkty: " + o.punkty; }
  return "Błąd: " + o.powod;
}
console.log(pokaz({ status: "blad", powod: "Mecz odwołany" }));
wynik w konsoli
Błąd: Mecz odwołany

To precyzyjniejsze niż punkty?: number i powod?: string w jednym obiekcie. Wariant ok wymaga punktów, a wariant blad wymaga powodu.

PRZYKŁAD Z OMÓWIENIEM

Panel meczu w konsoli

Trzy stany mają różne dane. Użyjemy switch, aby każdy stan dostał własny komunikat.

src/index.ts — osobny przykład
type Mecz =
  | { stan: "plan"; godzina: string }
  | { stan: "koniec"; gospodarz: number; gosc: number }
  | { stan: "odwolany"; powod: string };
function komunikat(mecz: Mecz): string {
  switch (mecz.stan) {
    case "plan": return "Start: " + mecz.godzina;
    case "koniec": return mecz.gospodarz + ":" + mecz.gosc;
    case "odwolany": return "Odwołano: " + mecz.powod;
  }
}
const mecze: Mecz[] = [
  { stan: "plan", godzina: "16:00" },
  { stan: "koniec", gospodarz: 3, gosc: 2 },
  { stan: "odwolany", powod: "Brak prądu" }
];
for (const mecz of mecze) { console.log(komunikat(mecz)); }
wynik w konsoli
Start: 16:00
3:2
Odwołano: Brak prądu

Jak program dochodzi do wyniku

  1. Mecz to unia trzech kształtów obiektów. Każdy ma pole stan.
  2. Każdy case zawęża cały obiekt, a nie tylko tekst stan.
  3. return kończy funkcję, więc nie potrzebujemy break.
  4. Typ tablicy pozwala trzymać wszystkie warianty i przekazać każdy do tej samej funkcji.

Zanim przejdziesz dalej

Dlaczego w case „plan” nie odczytujemy mecz.gospodarz?

Sprawdź swoje rozumowanie

W tym stanie wynik nie istnieje. Kompilator pilnuje, żeby nie wyświetlać wyniku przed rozegraniem meczu.

EKSPERYMENT

Zmień kod i zobacz, co się stanie

in i własny strażnik

Jeżeli warianty nie mają wspólnego wyróżnika, możesz sprawdzić obecność pola. Zapis x is Kot informuje kompilator, co oznacza true. Musisz napisać zgodny z tą obietnicą warunek.

src/index.ts — osobny przykład
type Kot = { miaucz: () => string };
type Pies = { szczekaj: () => string };
function czyKot(x: Kot | Pies): x is Kot { return "miaucz" in x; }
function glos(x: Kot | Pies): string {
  if (czyKot(x)) { return x.miaucz(); }
  return x.szczekaj();
}
console.log(glos({ miaucz: () => "Miau" }));
console.log(glos({ szczekaj: () => "Hau" }));
wynik w konsoli
Miau
Hau

Ten strażnik zakłada wejście już zgodne z Kot | Pies. Samo in nie sprawdza, czy pole jest funkcją. Nie stosuj go jako kompletnej walidacji nieznanego JSON-u; do tego wrócimy w lekcji 29.

CZĘSTE BŁĘDY

Gdy wynik zaskakuje

PomyłkaCo się dzieje i jak to poprawić
Unia traktowana jak „wszystkie pola naraz”Wartość ma jeden z dopuszczonych kształtów. Najpierw rozpoznaj wariant.
Strażnik zawsze zwracający trueKompilator ufa deklaracji is. Błędna implementacja może doprowadzić do awarii.
typeof nullDaje „object”. Przy nieznanych danych osobno wyklucz null.
ZADANIA

Teraz Twoja kolej

Rozwiązanie jest jedną z możliwości. Zanim je odkryjesz, uruchom własną wersję dla podanych danych i przynajmniej jednego przypadku brzegowego.

ZAD 1Kod przesyłki★☆☆

Przyjmij number | string. Liczbę poprzedź #, a tekst zamień na wielkie litery. Sprawdź 5 i „ab”.

Mała podpowiedź

typeof rozdziela dwa warianty.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
function kod(x: number | string): string {
  return typeof x === "number" ? "#" + x : x.toUpperCase();
}
console.log(kod(5), kod("ab"));
wynik w konsoli
#5 AB

Każda gałąź zwraca tekst, mimo różnych typów wejścia.

ZAD 2Dostęp do serwera★★☆

Opisz rolę jako „gracz” | „moderator”. Funkcja ma zwracać true tylko dla moderatora.

Mała podpowiedź

Unia literałów zamyka listę dopuszczalnych tekstów.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
type Rola = "gracz" | "moderator";
function mozeUsunac(rola: Rola): boolean { return rola === "moderator"; }
console.log(mozeUsunac("gracz"), mozeUsunac("moderator"));
wynik w konsoli
false true

Literówka w roli zostanie wykryta podczas sprawdzania kodu.

ZAD 3Płatność przyjęta lub odrzucona★★☆

Zbuduj unię z wyróżnikiem ok: true albo false. Sukces ma numer, błąd ma powod. Wypisz oba przypadki.

Mała podpowiedź

Wyróżnik nie musi być tekstem.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
type Platnosc = { ok: true; numer: number } | { ok: false; powod: string };
function opis(p: Platnosc): string {
  if (p.ok) { return "Przyjęto " + p.numer; }
  return "Odmowa: " + p.powod;
}
console.log(opis({ ok: true, numer: 7 }));
console.log(opis({ ok: false, powod: "Brak monet" }));
wynik w konsoli
Przyjęto 7
Odmowa: Brak monet

Nie da się poprawnie opisać sukcesu bez numeru — tego wymaga wariant typu.

ZAD 4Biblioteka multimediów★★★

Film ma rodzaj „film” i minuty, gra rodzaj „gra” i poziomy. Przygotuj wspólną funkcję opisu i tablicę obu obiektów.

Mała podpowiedź

switch po polu rodzaj.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
type Media = { rodzaj: "film"; minuty: number } | { rodzaj: "gra"; poziomy: number };
function opis(m: Media): string {
  switch (m.rodzaj) {
    case "film": return m.minuty + " minut";
    case "gra": return m.poziomy + " poziomów";
  }
}
const biblioteka: Media[] = [{ rodzaj: "film", minuty: 90 }, { rodzaj: "gra", poziomy: 12 }];
biblioteka.forEach(m => console.log(opis(m)));
wynik w konsoli
90 minut
12 poziomów

Wspólny typ pozwala przechowywać różne media, a wyróżnik utrzymuje poprawne połączenia pól.

PODSUMOWANIE

Co zabierasz do następnej lekcji

  • Unia opisuje wybór; zawężanie ustala pasujący wariant.
  • Wyróżnik łączy stan z właściwymi dla niego danymi.
  • Własny strażnik jest obietnicą, której poprawność musisz sprawdzić.