Lekcje modułu (2/2)
Podstawy typów generycznych
Typy generyczne / Generics (programowanie uogólnione) są jedną z najpotężniejszych funkcji języka TypeScript. Pozwalają one na tworzenie elastycznych, reużywalnych komponentów, funkcji i interfejsów, które mogą współpracować z różnymi typami danych, zachowując jednocześnie maksymalne bezpieczeństwo typów (type safety) i unikając stosowania typu any.
Funkcje Generyczne
Pomyśl o typie generycznym jak o zmiennej przechowującej typ. Rozważmy funkcję, która zwraca przekazaną do niej wartość:
function identity<T>(arg: T): T {
return arg;
}Litera T (skrót od Type) to symbol zastępczy (placeholder) dla typu. Kiedy wywołujemy identity, TypeScript przechwytuje typ przekazanego argumentu i automatycznie przypisuje go do T:
const str = identity<string>('Hello'); // T è string
const num = identity(42); // T è number (inferito automaticamente!)Interfejsy i Aliasy Generyczne
Typy generyczne można stosować także do interfejsów i aliasów typów, co pozwala na definiowanie generycznych kontenerów lub struktur danych:
interface Box<T> {
content: T;
}
const stringBox: Box<string> = { content: 'TypeScript' };
const numberBox: Box<number> = { content: 42 };Wielokrotne Parametry Typów
Możemy definiować funkcje lub typy korzystające z wielu zmiennych typów (np. T i U):
function mergeObjects<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}Ograniczenia Typów Generycznych (Generic Constraints)
Czasami chcemy ograniczyć typy akceptowane przez parametr generyczny. Możemy użyć słowa kluczowego extends, aby dodać ograniczenie (constraint).
Na przykład, jeśli chcemy, aby typ generyczny zawsze posiadał właściwość length (tak jak napisy czy tablice):
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length); // Sicuro! Sappiamo che arg ha 'length'
}
logLength('Hello'); // Valido
logLength([1, 2, 3]); // Valido
// logLength(42); // Errore: number non ha la proprietà length!Spróbuj sam
Ćwiczenie 1: Generyczna funkcja identity
Uzupełnij funkcję generyczną identity tak, aby przyjmowała parametr value o typie generycznym T i zwracała go bez żadnych modyfikacji.
Pokaż wskazówkę
Typ zwracany przez funkcję musi być tym samym typem generycznym T co typ parametru.
Rozwiązanie dostępne po 3 próbach
Ćwiczenie 2: Generyczny Box
Zdefiniuj interfejs generyczny o nazwie Box, który przyjmuje parametr typu T i posiada jedną właściwość o nazwie content typu T.
Pokaż wskazówkę
Użyj składni interface Box<T> { content: T; }.
Rozwiązanie dostępne po 3 próbach
Ćwiczenie 3: Ograniczenie Typu Generycznego (Constraint)
Napisz funkcję generyczną o nazwie getLength, która przyjmuje parametr arg. arg must być ograniczony do interfejsu posiadającego właściwość length (liczba). Funkcja powinna zwracać arg.length.
Pokaż wskazówkę
Zadeklaruj funkcję jako getLength<T extends HasLength>(arg: T): number i zwróć arg.length.
Rozwiązanie dostępne po 3 próbach
Ćwiczenie 4: Generyczna para Pair
Zdefiniuj interfejs generyczny Pair<T, U> z dwiema właściwościami: first typu T oraz second typu U. Następnie utwórz funkcję generyczną makePair, która przyjmuje parametry first (typu T) oraz second (typu U) i zwraca obiekt implementujący interfejs Pair<T, U>.
Pokaż wskazówkę
Użyj składni z dwoma parametrami generycznymi Pair<T, U> i odpowiednio zdefiniuj makePair.
Rozwiązanie dostępne po 3 próbach