Obiekty, type i interface
Nick, punkty i wyposażenie należą do jednego gracza. Połącz je w obiekt, a jego kształt opisz tak, żeby literówki wykrywał edytor.
Po tej lekcji potrafisz
- Opiszesz obiekt przez type oraz interface.
- Dodasz pola opcjonalne, readonly i zagnieżdżone dane.
- Odróżnisz kopię obiektu od drugiej referencji do niego.
Dane gracza rozrzucone po programie
Osobne tablice nicków, punktów i poziomów wymagają pilnowania zgodnych indeksów. Obiekt trzyma powiązane informacje razem. Typ jest opisem wymagań dla takiego obiektu; nie tworzy sam gracza ani nie pobiera danych.
Małe kroki, konkretne efekty
Nazwij kształt danych
Alias type pozwala nadać nazwę opisowi. Wymieniasz pola oraz ich typy, a następnie sprawdzasz obiekty względem tej umowy.
type Gracz = { nick: string; punkty: number };
const gracz: Gracz = { nick: "Pixel", punkty: 40 };
gracz.punkty += 10;
console.log(gracz.nick + ": " + gracz.punkty);
Pixel: 50
const chroni przypisanie zmiennej, nie pola obiektu. Nadal wolno zmienić punkty. Próba odczytu gracz.punktyy zostanie wykryta jako literówka.
Interface, opcjonalność i readonly
Interface również opisuje obiekty. Pytajnik dopuszcza brak pola. readonly zabrania przypisywania do pola przez ten typ podczas sprawdzania kodu; nie zamraża obiektu w działającym JavaScripcie.
interface Profil {
readonly id: number;
nick: string;
opis?: string;
}
const profil: Profil = { id: 1, nick: "Ala" };
if (profil.opis !== undefined) { console.log(profil.opis); }
console.log(profil.id, profil.nick);
1 Ala
Do opisania unii, np. number | string, użyj type. Interface może rozszerzać inne interfejsy, a deklaracje tej samej nazwy mogą się łączyć. W kursie unikamy takiego łączenia, żeby kontrakt był widoczny w jednym miejscu.
Ekwipunek bohatera
Gracz posiada plecak opisany zagnieżdżonym obiektem. Funkcja otrzymuje całego gracza, zamiast kilku luźnych argumentów.
interface Plecak { monety: number; przedmioty: string[] }
interface Gracz { nick: string; plecak: Plecak }
function raport(gracz: Gracz): string {
return gracz.nick + ": " + gracz.plecak.monety + " monet, "
+ gracz.plecak.przedmioty.length + " przedmioty";
}
const gracz: Gracz = {
nick: "Pixel",
plecak: { monety: 30, przedmioty: ["mapa", "latarka"] }
};
console.log(raport(gracz));
const { nick } = gracz;
console.log("Witaj, " + nick);
Pixel: 30 monet, 2 przedmioty
Witaj, Pixel
Jak program dochodzi do wyniku
- Plecak opisuje dwa pola, z których przedmioty jest tablicą tekstów.
- Gracz wykorzystuje już nazwany typ Plecak; opis nie musi być wielokrotnie kopiowany.
- Dostęp gracz.plecak.monety przechodzi przez dwie warstwy obiektów.
- Destrukturyzacja wydobywa nick do zmiennej o tej samej nazwie. Nie usuwa pola z obiektu.
Zanim przejdziesz dalej
Czy samo interface Gracz tworzy obiekt w pamięci?
Sprawdź swoje rozumowanie
Nie. Interfejs znika podczas kompilacji. Obiekt powstaje dopiero przez literał { … } albo np. konstruktor klasy.
Zmień kod i zobacz, co się stanie
Dwie nazwy mogą wskazywać ten sam obiekt
Przypisanie obiektu nie robi kopii. Spread tworzy nowy obiekt z kopiami wartości pól. To kopia płytka: zagnieżdżone obiekty nadal mogą być wspólne.
const pierwszy = { nick: "Ala", staty: { punkty: 1 } };
const drugi = pierwszy;
drugi.nick = "Pixel";
const kopia = { ...pierwszy };
kopia.staty.punkty = 9;
console.log(pierwszy.nick, pierwszy.staty.punkty);
const osobny = { ...pierwszy, staty: { ...pierwszy.staty } };
osobny.staty.punkty = 20;
console.log(pierwszy.staty.punkty, osobny.staty.punkty);
Pixel 9
9 20
Aby oddzielić statystyki, kopiujemy również staty. Nie jest to uniwersalna recepta na głębokie kopiowanie dowolnego obiektu — tutaj znamy dokładnie dwie warstwy danych.
Gdy wynik zaskakuje
| Pomyłka | Co się dzieje i jak to poprawić |
|---|---|
| Mylenie type z wartością | Nie wykonasz console.log(Gracz), jeśli Gracz jest tylko aliasem typu. Wypisz zmienną gracz. |
| readonly uznane za ochronę wszystkich wnętrz | readonly plecak blokuje podmianę pola, ale nie zapewnia automatycznie readonly dla monet wewnątrz. |
| Spread uznany za głęboką kopię | Zagnieżdżone obiekty trzeba rozpatrywać osobno. |
Pola, których może nie być
Pole opcjonalne wiek?: number daje typ number | undefined. Wystarczy pamiętać, że
kompilator nie pozwoli użyć takiego pola bez sprawdzenia.
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.
Opisz grę polami tytul, cena i kooperacja. Utwórz obiekt i wypisz tytuł oraz cenę 49.
Mała podpowiedź
kooperacja ma tylko dwie możliwości, więc boolean.
Przykładowe rozwiązanie z wyjaśnieniem
type Gra = { tytul: string; cena: number; kooperacja: boolean };
const gra: Gra = { tytul: "Kosmos", cena: 49, kooperacja: true };
console.log(gra.tytul, gra.cena);
Kosmos 49
Jeden nazwany opis można wykorzystać dla całego katalogu gier.
Napisz opisProduktu. Zwróć opis, gdy istnieje; w przeciwnym razie „Brak opisu”. Sprawdź oba przypadki.
Mała podpowiedź
W interfejsie opis?: string, w funkcji porównanie z undefined.
Przykładowe rozwiązanie z wyjaśnieniem
interface Produkt { nazwa: string; opis?: string }
function opisProduktu(p: Produkt): string {
if (p.opis === undefined) { return "Brak opisu"; }
return p.opis;
}
console.log(opisProduktu({ nazwa: "Pad" }));
console.log(opisProduktu({ nazwa: "Pad", opis: "Bezprzewodowy" }));
Brak opisu
Bezprzewodowy
Opcjonalny opis nie wymusza sztucznego pustego tekstu.
Utwórz funkcję awans, która zwróci nowy obiekt z poziomem większym o 1. Oryginał ma zachować poziom 3.
Mała podpowiedź
Spread i pole poziom zapisane po nim.
Przykładowe rozwiązanie z wyjaśnieniem
type Bohater = { nick: string; poziom: number };
function awans(b: Bohater): Bohater { return { ...b, poziom: b.poziom + 1 }; }
const stary = { nick: "Neo", poziom: 3 };
const nowy = awans(stary);
console.log(stary.poziom, nowy.poziom);
3 4
Pole zapisane później nadpisuje wartość skopiowaną przez spread.
Opisz tablicę graczy z nickiem i monetami. Policz sumę monet funkcją bez zmieniania danych. Sprawdź też pustą drużynę.
Mała podpowiedź
Parametr Gracz[] i akumulator liczbowy.
Przykładowe rozwiązanie z wyjaśnieniem
interface Gracz { nick: string; monety: number }
function skarbiec(gracze: Gracz[]): number {
let suma = 0;
for (const gracz of gracze) { suma += gracz.monety; }
return suma;
}
console.log(skarbiec([{ nick: "A", monety: 5 }, { nick: "B", monety: 8 }]));
console.log(skarbiec([]));
13
0
Funkcja odczytuje monety, ale ich nie przypisuje, więc nie zmienia graczy.
Co zabierasz do następnej lekcji
- Obiekt jest wartością, a type i interface opisują jej kształt.
- Pole opcjonalne może być nieobecne. readonly jest ograniczeniem kontroli typów.
- Przypisanie nie kopiuje obiektu, a spread kopiuje tylko jeden poziom.