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.

CEL LEKCJI

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.
WPROWADZENIE

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.

POZNAJ SKŁADNIĘ

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.

src/index.ts — osobny przykład
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");
wynik w konsoli
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.

src/index.ts — osobny przykład
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"));
wynik w konsoli
7
Ala

keyof T to dopuszczalne klucze typu. T[K] oznacza typ wartości pod wybranym kluczem. Próba podania „nikk” zostanie wykryta.

PRZYKŁAD Z OMÓWIENIEM

Pudełko na dane dowolnego rodzaju

Dwa pudełka mają tę samą implementację, ale różne ograniczenia wartości. Jedno przechowuje tekst, drugie liczbę.

src/index.ts — osobny przykład
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));
wynik w konsoli
NEO
25.0

Jak program dochodzi do wyniku

  1. Pierwsza instancja ma T = string, druga T = number.
  2. zamien wymaga tego samego typu, który przyjęło pudełko.
  3. odczytaj zachowuje informację o wyniku, więc podpowiedzi metod są poprawne.
  4. 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.

EKSPERYMENT

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.

src/index.ts — osobny przykład
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); }
wynik w konsoli
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.

CZĘSTE BŁĘDY

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.
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 1Echo z typem★☆☆

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
src/index.ts — osobny przykład
function echo<T>(x: T): T { return x; }
console.log(echo(5), echo("hej"));
wynik w konsoli
5 hej

To ćwiczenie składni. W praktyce samo echo rzadko jest potrzebne.

ZAD 2Ostatni element★★☆

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
src/index.ts — osobny przykład
function ostatni<T>(lista: T[]): T | undefined { return lista.at(-1); }
console.log(ostatni(["A", "B"]));
console.log(ostatni<number>([]) ?? "Brak");
wynik w konsoli
B
Brak

Typ wyniku nie ukrywa pustej listy.

ZAD 3Wspólna długość★★☆

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
src/index.ts — osobny przykład
function dlugosc<T extends { length: number }>(x: T): number { return x.length; }
console.log(dlugosc("abc"), dlugosc([1, 2]));
wynik w konsoli
3 2

Funkcji wystarcza wspólna właściwość, nie musi znać całego typu.

ZAD 4Para różnych skarbów★★★

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
src/index.ts — osobny przykład
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));
wynik w konsoli
KLUCZ 3

Wynik nie jest luźną tablicą unii. Pozycje krotki zachowują różne typy.

PODSUMOWANIE

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.