programowanieFrontend Developer

Jak TypeScript działa z user-defined type guards (samodzielnie zdefiniowanymi funkcjami ochrony typów), do czego są potrzebne i jak tworzyć poprawne funkcje szeregów typów? Jakie typowe pułapki występują przy ich implementacji?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

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:

  • Sprawdzanie typu realizowane jest za pomocą specjalnych funkcji szeregów typów w formie param is Type.
  • Można tworzyć własne strażnicy i używać ich do dowolnych struktur, nie tylko do obiektów klasowych.
  • Niepoprawne strażnicy typów mogą prowadzić do błędów w czasie wykonywania i nieprawidłowego ograniczenia typu.

Pytania ze zwrotem.

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.

Typowe błędy i anty-wzorce

  • Strażnik typów nie zwraca typu szeregów typów, tylko boolean — brak efektu dla kompilatora.
  • Sprawdzają tylko powierzchowne klucze, nie gwarantując integralności strukturalnej.
  • Strażnik typów zawsze zwraca true, faktycznie wyłączając ochronę typów.
  • Parametr wejściowy jest zbyt ogólny, lub dokonuje się niejawnego rzutowania typu bez walidacji.

Przykład z życia

Negatywny przypadek

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:

  • Szybko, efekt widoczny w czasie działania

Wady:

  • Po filtracji typ danych nie zostaje doprecyzowany dla TypeScript, możliwe błędy przy odwołaniach do właściwości.

Pozytywny przypadek

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:

  • Typ wyników jest dokładnie znany
  • Zmniejsza prawdopodobieństwo błędów podczas dalszej pracy z wynikiem

Wady:

  • Wymaga nieco większej uwagi do sygnatury i pokrycia testami.