Type guard to sprawdzenie w czasie wykonania, które TypeScript rozumie, więc zawęża typ w sprawdzonej gałęzi. typeof, instanceof i in to wbudowane type guardy. Do wszystkiego innego piszesz funkcję, której typ zwracany to predykat typu, value is Type.
isUser w czasie wykonania zwraca zwykły boolean. Typ zwracany value is User mówi kompilatorowi, czego dowodzi wynik true, i każde if (isUser(x)) zawęża potem x do User.
Wbudowane type guardy
Te sprawdzenia zawężają typ bez żadnej funkcji pomocniczej:
| Guard | Przykład | Do czego |
|---|---|---|
typeof | typeof x === "number" | typy proste i funkcje |
instanceof | x instanceof Date | instancje klas |
in | "email" in x | unie obiektów, właściwości obiektów unknown |
Array.isArray | Array.isArray(x) | tablice |
| Równość | x === null, x.kind === "circle" | null/undefined, znaczniki z literałem |
| Truthiness | if (x) | usuwanie null i undefined |
Wszystkie działają jako zwykły JavaScript. TypeScript dodaje zawężanie: odczytuje sprawdzenie i dopasowuje typ w każdej gałęzi. Pełną listę form znajdziesz na stronie o zawężaniu typów. Własny guard przydaje się do sprawdzeń, które nie mieszczą się w jednym wyrażeniu albo które chcesz używać wielokrotnie.
Pisanie predykatu typu
Predykat typu ma postać parameterName is Type i zastępuje boolean jako typ zwracany. Zawężanie działa w obie strony: true zawęża do Type, a false usuwa Type z unii.
Przekazanie guarda do filter daje poprawnie otypowaną tablicę. Od TypeScript 5.5 kompilator sam wnioskuje predykat z prostych funkcji strzałkowych, więc pets.filter((p) => p.kind === "cat") zwraca Cat[] bez nazwanego guarda.
Typ predykatu musi pasować do typu parametru: function f(x: string): x is number to błąd TS2677, A type predicate's type must be assignable to its parameter's type.
Kompilator ufa twojemu guardowi
TypeScript sprawdza, czy guard zwraca boolean. Nie sprawdza, czy ten boolean jest prawidłowy. Guard, który zwraca true dla złych wartości, sprawia, że typy kłamią, a program zawodzi w czasie wykonania bez żadnego błędu kompilacji.
data.price.toFixed(2) rzuca w czasie wykonania TypeError: Cannot read properties of undefined (reading 'toFixed'). Kompilator uznał data.price za number, bo tak powiedział guard. Sprawdzaj każdą właściwość, na której polega reszta kodu, a guardy trzymaj małe, przetestowane i blisko typu, który opisują.
Sprawdzanie, czy obiekt ma dany typ
To pytanie stoi za większością własnych guardów: dane przychodzą jako unknown (z JSON.parse, fetch, localStorage, wiadomości) i musisz wiedzieć, czy pasują do twojego interfejsu. Przepis:
typeof value === "object" && value !== null(obiekt, a nienull)."prop" in valuedla każdej wymaganej właściwości. Naunknownoperatorindodaje właściwość do typu jakounknown.typeof value.prop === "..."(albo zagnieżdżony guard) dla typu każdej właściwości.Array.isArray(value.items) && value.items.every(isItem)dla tablic.
Przy dużych albo głęboko zagnieżdżonych kształtach pisanie tego ręcznie staje się żmudne. Biblioteki schematów, takie jak Zod albo Valibot, pozwalają opisać kształt raz i dają zarówno sprawdzenie w czasie wykonania, jak i typ TypeScript.
Funkcje asercji: asserts value is Type
Funkcja asercji rzuca wyjątek, gdy sprawdzenie się nie powiedzie, a w przeciwnym razie normalnie kończy działanie. Jej typ zwracany to asserts value is Type (albo asserts condition), a wszystko po wywołaniu jest zawężone, bez potrzeby pisania if.
Jedna reguła często zaskakuje: funkcję asercji trzeba wywołać przez nazwę z jawnym typem. Funkcja strzałkowa w const bez adnotacji, const check = (v: unknown): asserts v is string => {...}, daje w miejscu wywołania błąd TS2775, Assertions require every name in the call target to be declared with an explicit type annotation. Użyj deklaracji function albo dodaj do stałej adnotację typu funkcji.
Guardy, asercje i rzutowania
| Narzędzie | Sprawdzenie w czasie wykonania? | Zawęża | Gdy się nie uda |
|---|---|---|---|
Wbudowany guard (typeof, in...) | Tak | w gałęzi | wybiera drugą gałąź |
Funkcja value is T | Tak (twój kod) | w gałęzi | wybiera drugą gałąź |
Funkcja asserts value is T | Tak (twój kod) | po wywołaniu | rzuca wyjątek |
value as T | Nie | wyrażenie | nic: zły typ się rozprzestrzenia |
Asercja typu (as) zmienia typ bez żadnego sprawdzenia. Na granicy, gdzie dane przychodzą z zewnątrz, guard albo funkcja asercji to bezpieczna wersja tego samego pomysłu.
Guardy oparte na this w klasach
Metoda może zawęzić obiekt, na którym jest wywoływana, przez this is Type. Przydaje się to w hierarchiach klas:
class FileNode {
constructor(public name: string) {}
isDirectory(): this is DirectoryNode {
return this instanceof DirectoryNode;
}
}
class DirectoryNode extends FileNode {
children: FileNode[] = [];
}
function count(node: FileNode): number {
return node.isDirectory() ? node.children.length : 0; // node: DirectoryNode in the true branch
}
Najczęściej zadawane pytania
Czym jest type guard w TypeScript?
To każde sprawdzenie w czasie wykonania, którego TypeScript używa do zawężenia typu: typeof x === "string", x instanceof Date, "id" in x, Array.isArray(x) albo wywołanie funkcji, której typ zwracany to predykat typu, na przykład x is User. W sprawdzonej gałęzi zmienna ma węższy typ.
Jak sprawdzić, czy obiekt ma dany typ w TypeScript?
Typy nie istnieją w czasie wykonania, więc sprawdzasz właściwości: napisz funkcję isUser(value: unknown): value is User, która testuje typeof value === "object", value !== null i każdą wymaganą właściwość przez in i typeof. Po if (isUser(x)) zmienna x ma typ User. Dla klas wystarczy x instanceof MyClass.
Co oznacza "value is Type" w TypeScript?
To predykat typu, używany jako typ zwracany funkcji. W czasie wykonania funkcja nadal zwraca boolean, ale gdy zwraca true, TypeScript zawęża argument do Type w miejscu wywołania, a gdy zwraca false, zawęża go do pozostałych elementów unii. Kompilator nie weryfikuje ciała funkcji, więc sprawdzenie musi być poprawne.
Czym różni się type guard od funkcji asercji?
Type guard (x is T) zwraca boolean i zawęża typ wewnątrz if. Funkcja asercji (asserts x is T) nic nie zwraca i rzuca wyjątek, gdy sprawdzenie się nie powiedzie, więc wszystko po wywołaniu jest zawężone bez if. Guardów używaj do rozgałęziania, a asercji do sytuacji "to musi być prawdą, inaczej stop".
Czy mogę sprawdzić, czy obiekt implementuje interfejs w TypeScript?
Nie bezpośrednio: interfejsy są usuwane przy kompilacji, a instanceof ich nie przyjmuje. Napisz type guard, który sprawdza właściwości interfejsu, albo dodaj właściwość-znacznik z literałem (kind: "user") i ją porównuj. Biblioteki schematów, takie jak Zod, generują zarówno sprawdzenie, jak i typ z jednej definicji.