Generyki — zachowaj informację o typie
Ta sama funkcja może obsłużyć tablicę liczb i nicków bez any. Parametr typu pozwala zapamiętać związek między wejściem a wynikiem.
Po tej lekcji potrafisz
- Zapiszesz funkcję i klasę z parametrem typu.
- Dodasz ograniczenie extends oraz wybór klucza przez keyof.
- Wyjaśnisz, dlaczego generyk nie sprawdza danych w runtime.
Pierwszy element różnych list
Lista wyników zwraca liczbę, lista nicków tekst. Pisanie dwóch prawie identycznych funkcji jest zbędne, ale any usunęłoby podpowiedzi. Generyk zapisuje wspólny przepis i zachowuje konkretny typ przy wywołaniu.
Małe kroki, konkretne efekty
T jest parametrem typu
T nie jest wartością w programie. To nazwa typu wybranego dla danego użycia. Zapisujemy go przy deklaracji funkcji i używamy w typach wejścia oraz wyniku.
function pierwszy<T>(lista: T[]): T | undefined { return lista[0]; }
console.log(pierwszy([10, 20]));
console.log(pierwszy(["Ala", "Neo"]));
console.log(pierwszy<number>([]) ?? "Pusto");
10
Ala
Pusto
Kompilator zwykle wywnioskuje T z argumentu. Przy pustej tablicy jawny parametr number opisuje planowany rodzaj danych. undefined uczciwie uwzględnia brak pierwszego elementu.
Ograniczenie daje dostęp do określonych pól
T bez ograniczenia może być niemal czymkolwiek, więc nie zakładaj istnienia length czy id. extends w generyku ogranicza dopuszczalne typy. Nie oznacza tutaj dziedziczenia klasy.
function identyfikator<T extends { id: number }>(obiekt: T): number {
return obiekt.id;
}
console.log(identyfikator({ id: 7, nick: "Pixel" }));
function pole<T, K extends keyof T>(obiekt: T, klucz: K): T[K] {
return obiekt[klucz];
}
console.log(pole({ nick: "Ala", punkty: 12 }, "nick"));
7
Ala
keyof T to dopuszczalne klucze typu. T[K] oznacza typ wartości pod wybranym kluczem. Próba podania „nikk” zostanie wykryta.
Pudełko na dane dowolnego rodzaju
Dwa pudełka mają tę samą implementację, ale różne ograniczenia wartości. Jedno przechowuje tekst, drugie liczbę.
class Pudelko<T> {
constructor(private zawartosc: T) {}
odczytaj(): T { return this.zawartosc; }
zamien(nowa: T): void { this.zawartosc = nowa; }
}
const nick = new Pudelko("Pixel");
const punkty = new Pudelko(10);
nick.zamien("Neo");
punkty.zamien(25);
console.log(nick.odczytaj().toUpperCase());
console.log(punkty.odczytaj().toFixed(1));
NEO
25.0
Jak program dochodzi do wyniku
- Pierwsza instancja ma T = string, druga T = number.
- zamien wymaga tego samego typu, który przyjęło pudełko.
- odczytaj zachowuje informację o wyniku, więc podpowiedzi metod są poprawne.
- W JavaScripcie nie ma osobnej klasy wygenerowanej dla każdego T.
Zanim przejdziesz dalej
Czy nick.zamien(123) jest prawidłowe?
Sprawdź swoje rozumowanie
Nie. Pudełko nick przyjęło typ string. To powiązanie jest właśnie korzyścią względem any.
Zmień kod i zobacz, co się stanie
Generyczny wynik operacji
Alias generyczny może połączyć unię stanów ze zmiennym rodzajem danych. Sukces z liczbą i sukces z tekstem korzystają z tego samego opisu.
type Wynik<T> = { ok: true; dane: T } | { ok: false; blad: string };
function sukces<T>(dane: T): Wynik<T> { return { ok: true, dane }; }
const wynik = sukces([10, 20]);
if (wynik.ok) { console.log(wynik.dane.length); }
2
Wynik<T> nie potwierdza prawdziwości danych z sieci. T znika po kompilacji, tak jak inne typy. Walidacja nadal wymaga kodu wykonywanego przez program.
Gdy wynik zaskakuje
| Pomyłka | Co się dzieje i jak to poprawić |
|---|---|
| Odczyt length z nieograniczonego T | Dodaj extends { length: number }, jeśli właśnie tej właściwości potrzebujesz. |
| Generyk z wynikiem any | Tracisz związek między wejściem a wyjściem. Zwróć T lub odpowiedni typ zależny. |
| Zbyt wiele parametrów typu | Dodawaj je, gdy opisują rzeczywistą zależność. Zwykła funkcja często wystarcza. |
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.
Napisz echo<T>, które zwraca argument bez zmiany. Wywołaj dla 5 i „hej”.
Mała podpowiedź
T występuje przy parametrze oraz wyniku.
Przykładowe rozwiązanie z wyjaśnieniem
function echo<T>(x: T): T { return x; }
console.log(echo(5), echo("hej"));
5 hej
To ćwiczenie składni. W praktyce samo echo rzadko jest potrzebne.
Napisz ostatni<T> dla tablicy. Sprawdź nicki i pustą tablicę liczb.
Mała podpowiedź
Wynik T | undefined i at(-1).
Przykładowe rozwiązanie z wyjaśnieniem
function ostatni<T>(lista: T[]): T | undefined { return lista.at(-1); }
console.log(ostatni(["A", "B"]));
console.log(ostatni<number>([]) ?? "Brak");
B
Brak
Typ wyniku nie ukrywa pustej listy.
Funkcja dlugosc ma przyjmować tekst lub tablicę dzięki ograniczeniu { length: number }. Sprawdź „abc” i [1, 2].
Mała podpowiedź
Nie wpisuj dwóch przeciążeń.
Przykładowe rozwiązanie z wyjaśnieniem
function dlugosc<T extends { length: number }>(x: T): number { return x.length; }
console.log(dlugosc("abc"), dlugosc([1, 2]));
3 2
Funkcji wystarcza wspólna właściwość, nie musi znać całego typu.
Zbuduj funkcję para<A, B> zwracającą krotkę [A, B]. Dla „klucz” i 3 odczytaj oba elementy z zachowaniem ich typów.
Mała podpowiedź
Dwa niezależne rodzaje danych wymagają dwóch parametrów typu.
Przykładowe rozwiązanie z wyjaśnieniem
function para<A, B>(a: A, b: B): [A, B] { return [a, b]; }
const [nazwa, liczba] = para("klucz", 3);
console.log(nazwa.toUpperCase(), liczba.toFixed(0));
KLUCZ 3
Wynik nie jest luźną tablicą unii. Pozycje krotki zachowują różne typy.
Co zabierasz do następnej lekcji
- Parametr typu opisuje zależność między typami danych.
- extends ogranicza dopuszczalne T; keyof i T[K] opisują dostęp do pól.
- Generyki nie zastępują walidacji podczas działania.