Historia pytania:
Wraz z rozwojem TypeScript pojawiła się potrzeba niezawodnego określania węższego typu zmiennej w gałęziach logicznych. Klasyczne sprawdzenia typów (przez typeof lub instanceof) nie zawsze są wystarczające, zwłaszcza gdy obiekt ma skomplikowaną strukturę lub hierarchię. Aby zwiększyć bezpieczeństwo danych i wygodę, TypeScript wprowadził mechanizm typów predykatów do tworzenia własnych strażników typów (type guards).
Problem:
Zwykłe sprawdzania typu wewnątrz funkcji nie dają kompilatorowi informacji o typie zmiennej w dalszym kodzie, jeśli użyjemy tylko wyniku true/false. Kompilator nie "rozumie", co dokładnie zostało sprawdzone. Prowadzi to do niejawnych błędów w czasie wykonywania, gdy omyłkowo odwołujemy się do nieistniejących właściwości.
Rozwiązanie:
Typy predykatów (type predicates) przy użyciu typu zastępczego w postaci 'param is Type' dają kompilatorowi do zrozumienia, że z tym parametrem po wywołaniu sprawdzenia można pracować jako z określonym typem. Takie funkcje zwiększają bezpieczeństwo typów i rozszerzają system zawężania typów do wszelkich skomplikowanych zadań.
Przykład kodu:
interface Bird { fly(): void; feathers: boolean; } interface Fish { swim(): void; fins: number; } function isBird(animal: Bird | Fish): animal is Bird { return (animal as Bird).fly !== undefined; } const pet: Bird | Fish = ...; if (isBird(pet)) { pet.fly(); // OK: pet teraz Bird } else { pet.swim(); // OK: pet teraz Fish }
Kluczowe cechy:
Czy funkcja type guard może działać, jeśli jawnie nie wskaże w sygnaturze zwracanego typu 'param is Type'?
Nie, jeśli nie wskaże się jawnie 'param is Type' w sygnaturze, TypeScript nie będzie w stanie zawęzić typu w gałęziach kodu, pomimo powracającej wartości true/false. Kompilator nie zrozumie, że parametr można używać jako określony typ.
Przykład kodu:
function isFish(animal: Fish | Bird): boolean { return (animal as Fish).swim !== undefined; } // Działa? if (isFish(pet)) { pet.swim(); // Błąd: Właściwość 'swim' nie istnieje }
Czy można używać typów predykatów do sprawdzania wartości prymitywnych, takich jak ciąg lub liczba?
Tak, można, ale częściej stosuje się typeof i takie strażnicy stają się zbędne. Niemniej jednak, nic nie stoi na przeszkodzie, aby zaimplementować własny strażnika:
function isString(x: unknown): x is string { return typeof x === "string"; }
Czy funkcja type guard zapewnia ścisłą ochronę przed błędami typu na etapie kompilacji?
Nie do końca. TypeScript polega na implementacji samej funkcji i nie może sprawdzić poprawności logiki w niej. Jeśli niepoprawnie zaimplementujesz sprawdzenie – kompilator nie zrozumie błędu, a problemy mogą wystąpić w czasie wykonywania.
function isFish(animal: Fish | Bird): animal is Fish { // Błędnie: zawsze zwraca true return true; }
** Negatywny przypadek Programista zaimplementował funkcję predykatu, ale popełnił błąd w sprawdzaniu struktury, przez co funkcja zawsze zwracała true. Kod przechodził kompilację, ale w czasie wykonania dochodziło do wywołania nieistniejącej metody.
Zalety:
Wady:
** Pozytywny przypadek Funkcje predykatów typów zaimplementowane poprawnie, przetestowane testami jednostkowymi na granicznych wartościach i błędnych danych.
Zalety:
Wady: