Die Geschichte der Frage:
Mit der Entwicklung von TypeScript entstand die Aufgabe, den Typ einer Variablen in logischen Zweigen zuverlässig enger zu definieren. Klassische Typprüfungen (über typeof oder instanceof) sind nicht immer ausreichend, insbesondere wenn ein Objekt eine komplexe Struktur oder Hierarchie hat. Um die Datensicherheit und Benutzerfreundlichkeit zu erhöhen, hat TypeScript einen Mechanismus für Typ-Behauptungen zur Erstellung benutzerdefinierter Type Guards implementiert.
Das Problem:
Gewöhnliche Typprüfungen innerhalb von Funktionen geben dem Compiler keine Informationen über den Typ der Variablen im nachfolgenden Code, wenn nur das Ergebnis true/false verwendet wird. Der Compiler „versteht“ nicht, was genau überprüft wurde. Das führt zu impliziten Fehlern zur Laufzeit, wenn wir versehentlich auf nicht vorhandene Eigenschaften zugreifen.
Die Lösung:
Typ-Behauptungen (Type Predicates) mit einem Typwechsel in der Form 'param is Type' ermöglichen es dem Compiler zu verstehen, dass mit diesem Parameter nach dem Aufruf der Prüfung wie mit einem bestimmten Typ gearbeitet werden kann. Solche Funktionen erhöhen die Typsicherheit und erweitern das System zur Typverengung für alle komplexen Aufgaben.
Beispielcode:
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 ist jetzt Bird } else { pet.swim(); // OK: pet ist jetzt Fish }
Wesentliche Merkmale:
Kann eine Type Guard-Funktion funktionieren, wenn der Rückgabewert 'param is Type' nicht explizit in der Signatur angegeben ist?
Nein, ohne die explizite Angabe von 'param is Type' in der Signatur kann TypeScript den Typ in den Codezweigen nicht verengen, unabhängig vom Rückgabewert true/false. Der Compiler versteht nicht, dass der Parameter als bestimmter Typ verwendet werden kann.
Beispielcode:
function isFish(animal: Fish | Bird): boolean { return (animal as Fish).swim !== undefined; } // Funktioniert es? if (isFish(pet)) { pet.swim(); // Fehler: Property 'swim' does not exist }
Kann man Type Predicates zur Prüfung primitiver Werte wie String oder Number verwenden?
Ja, das ist möglich, aber es wird häufiger typeof verwendet, und solche Guards werden überflüssig. Nichts hindert jedoch daran, einen benutzerdefinierten Guard zu implementieren:
function isString(x: unknown): x is string { return typeof x === "string"; }
Bietet eine Type Guard-Funktion einen strengen Schutz vor Typfehlern zur Compile-Zeit?
Nicht vollständig. TypeScript verlässt sich auf die Implementierung der Funktion selbst und kann die Logik innerhalb von ihr nicht auf Korrektheit überprüfen. Wenn Sie die Prüfung falsch implementieren, versteht der Compiler den Fehler nicht, und es treten zur Laufzeit Probleme auf.
function isFish(animal: Fish | Bird): animal is Fish { // Fehlerhaft: gibt immer true zurück return true; }
Negativer Fall Ein Entwickler implementierte eine Prädikatfunktion, machte jedoch einen Fehler in der Strukturprüfung, wodurch die Funktion immer true zurückgab. Der Code bestand die Kompilierung, aber zur Laufzeit wurde eine nicht vorhandene Methode aufgerufen.
Vorteile:
Nachteile:
Positiver Fall Type Predicate-Funktionen wurden korrekt implementiert und mit Unit-Tests an Grenzwerten und fehlerhaften Daten getestet.
Vorteile:
Nachteile: