Historia pytania: TypeScript rozszerza zwykłe sprawdzenia typów, pozwalając programistom na tworzenie swoich własnych funkcji — type guards — które sprawdzają, czy obiekt odpowiada określonemu typowi. Jest to niezbędne do pracy z typami unii, dynamicznymi strukturami i API, gdzie typ wartości może być różny.
Problem często polega na tym, że zwykłe sprawdzenia typu za pomocą typeof i instanceof są ograniczone do typów prymitywnych i klas, podczas gdy struktury lub złożone typy są trudne do zidentyfikowania. Należy umieć jasno zasugerować kompilatorowi, kiedy wartość jest bezpiecznie ograniczona do potrzebnego typu.
Rozwiązanie — pisanie funkcji ochronnych w formie function isCat(obj: Animal): obj is Cat {...}, gdzie kluczowym elementem jest szereg typów w zwracanym typie funkcji.
Przykład kodu:
interface Dog { bark: () => void; } interface Cat { meow: () => void; } type Pet = Dog | Cat; function isDog(pet: Pet): pet is Dog { return (pet as Dog).bark !== undefined; } function makeSound(pet: Pet) { if (isDog(pet)) { pet.bark(); } else { pet.meow(); } }
Kluczowe cechy:
Czy wystarczy w funkcji typu guard zwrócić true/false, aby kompilator ograniczył typ?
Nie. Ważne jest, aby wyraźnie określić zwracany typ w formie szeregów typów (przykład: pet is Dog), w przeciwnym razie TypeScript nie będzie automatycznie ograniczał typu wartości, nawet jeśli funkcja zwraca tylko true lub false.
Czy można używać typu guard wewnątrz callbacka (np. w filter), a czy ograniczenie będzie działać poprawnie?
Tak, jeśli typ guard jest prawidłowo anotowany, kompilator ograniczy typ elementów tablicy po filtrze i w funkcjach forEach/callback. Ale jeśli adnotacja jest nieobecna lub niepoprawna, wynik będzie mieć typ unii zamiast zdefiniowanego typu.
const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // TypeScript wie, że dogs: Dog[]
Czym różnią się użytkowe strażnice typów od zwykłych sprawdzeń typów za pomocą typeof, instanceof?
Funkcje strażników typów pozwalają na realizację sprawdzania dowolnej struktury, opisywanie sprawdzeń o dowolnym poziomie złożoności, operując interfejsami bez klasy, a nie tylko podstawowymi typami i klasami.
Funkcja filtruje użytkowników, tworząc typ guard bez szeregów typów:
function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]
Zalety:
Wady:
Funkcja filtruje z poprawnym szeregami typów:
interface Admin { name: string; isAdmin: true; } function isAdmin(user: any): user is Admin { return user && user.isAdmin === true; } const admins = users.filter(isAdmin); // admins: Admin[]
Zalety:
Wady: