Menu

Type guard w TypeScript: jak sprawdzić typ obiektu

Type guard to sprawdzenie w czasie wykonania, które TypeScript rozumie. Poznaj wbudowane type guardy, pisanie własnych z predykatem value is Type, sprawdzanie, czy obiekt ma dany typ, funkcje asercji z asserts i walidację danych typu unknown.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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:

GuardPrzykładDo czego
typeoftypeof x === "number"typy proste i funkcje
instanceofx instanceof Dateinstancje klas
in"email" in xunie obiektów, właściwości obiektów unknown
Array.isArrayArray.isArray(x)tablice
Równośćx === null, x.kind === "circle"null/undefined, znaczniki z literałem
Truthinessif (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:

  1. typeof value === "object" && value !== null (obiekt, a nie null).
  2. "prop" in value dla każdej wymaganej właściwości. Na unknown operator in dodaje właściwość do typu jako unknown.
  3. typeof value.prop === "..." (albo zagnieżdżony guard) dla typu każdej właściwości.
  4. 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ędzieSprawdzenie w czasie wykonania?ZawężaGdy się nie uda
Wbudowany guard (typeof, in...)Takw gałęziwybiera drugą gałąź
Funkcja value is TTak (twój kod)w gałęziwybiera drugą gałąź
Funkcja asserts value is TTak (twój kod)po wywołaniurzuca wyjątek
value as TNiewyrażenienic: 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ