ProgrammierungFrontend Entwickler

Wie funktioniert TypeScript mit benutzerdefinierten Typwächtern (self-defined type guards), wofür sind sie nötig und wie erstellt man korrekte type predicate-Funktionen? Welche typischen Fallen treten bei ihrer Implementierung auf?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort.

Umgang mit dem Thema: TypeScript erweitert die üblichen Typüberprüfungen, indem es Entwicklern ermöglicht, ihre eigenen Funktionen – type guards – zu erstellen, die überprüfen, ob ein Objekt einem bestimmten Typ entspricht. Dies ist notwendig, um mit Union-Typen, dynamischen Strukturen und APIs zu arbeiten, bei denen der Typ des Wertes variieren kann.

Das Problem ist oft, dass gewöhnliche Typüberprüfungen über typeof und instanceof auf primitive Typen und Klassen beschränkt sind, was es schwierig macht, Strukturen oder komplexe Typen zu bestimmen. Man muss in der Lage sein, dem Compiler explizit anzuzeigen, in welchen Fällen der Wert sicher auf den erforderlichen Typ eingeengt wird.

Die Lösung besteht darin, Wächterfunktionen in der Form function isCat(obj: Animal): obj is Cat {...} zu schreiben, wobei der Schlüsselpunkt der type predicate im Rückgabewert der Funktion ist.

Beispielcode:

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(); } }

Wichtige Merkmale:

  • Die Typüberprüfung erfolgt durch spezielle type predicate-Funktionen in der Form param is Type.
  • Man kann eigene Guards erstellen und diese für alle Strukturen verwenden, nicht nur für objektorientierte Klassen.
  • Falsche type guards können zu Laufzeitfehlern und falschen Typeneingrenzungen führen.

Fangfragen.

Reicht es aus, in der type guard-Funktion true/false zurückzugeben, damit der Compiler den Typ eingrenzt?

Nein. Es ist wichtig, den Rückgabetyp explizit als type predicate (zum Beispiel: pet is Dog) anzugeben, andernfalls wird TypeScript den Wertetyp nicht automatisch eingrenzen, selbst wenn die Funktion nur true oder false zurückgibt.

Kann man type guards innerhalb eines Callback (zum Beispiel in filter) verwenden, und funktioniert die Eingrenzung korrekt?

Ja, wenn der type guard korrekt annotiert ist, wird der Compiler den Typ der Array-Elemente nach filter und innerhalb von forEach/callback-Funktionen eingrenzen. Ist die Annotation jedoch fehlend oder falsch, hat das Ergebnis den Typ-Union anstelle eines konkretisierten Typs.

const pets: Pet[] = [...]; const dogs = pets.filter(isDog); // TypeScript weiß, dass dogs: Dog[]

Wie unterscheiden sich benutzerdefinierte type guards von gewöhnlichen Typüberprüfungen über typeof, instanceof?

Type guard-Funktionen ermöglichen die Überprüfung beliebiger Strukturen, das Beschreiben von Prüfungen beliebiger Komplexität und den Umgang mit Interfaces ohne Klassen, und nicht nur mit grundlegenden Typen und Klassen.

Typische Fehler und Anti-Pattern

  • Der type guard gibt keinen type-predicate-Typ zurück, sondern nur boolean – kein Effekt für den Compiler.
  • Es werden nur oberflächliche Schlüssel überprüft, womit die strukturelle Integrität nicht garantiert wird.
  • Der type guard gibt immer true zurück, was die Typüberprüfung praktisch deaktiviert.
  • Der Eingangsparameter ist zu allgemein oder es erfolgt eine implizite Typumwandlung ohne Validierung.

Praktisches Beispiel

Negativer Fall

Die Funktion filtert Benutzer und macht dabei einen type guard ohne type predicate:

function isValidAdmin(user: any): boolean { return user.isAdmin === true; } const admins = users.filter(isValidAdmin); // admins: any[]

Vorteile:

  • Schnell, der Effekt ist zur Laufzeit spürbar.

Nachteile:

  • Nach der Filterung wird der Datentyp für TypeScript nicht präzisiert, es können Fehler beim Zugriff auf Eigenschaften auftreten.

Positiver Fall

Die Funktion filtert mit korrektem type predicate:

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[]

Vorteile:

  • Der Typ der Ergebnisse ist genau bekannt.
  • Verringert die Wahrscheinlichkeit von Fehlern bei der weiteren Arbeit mit dem Ergebnis.

Nachteile:

  • Erfordert etwas mehr Aufmerksamkeit für die Signatur und für die Abdeckung der Prüfungen.