JavaScript hat ein dynamisches Typsystem, das seine Vorteile hat, allerdings auch seine Nachteile, um den Nachteilen etwas entgegenzusetzen, gibt es das Superset TypeScript und das Konzept von TypeGuards, auf Deutsch so viel wie der „Typen Wächter“. In diesem Beitrag zeige ich dir übliche Wege, um einen TypeGuard zu schreiben. Dabei ist egal, ob du mit TypeScript oder JavaScript arbeitest, beachten solltest du nur, dass der Einsatz von TypeGuards in Kombination mit TypeScript seine Stärken erst so richtig zeigen kann.
Ein dynamisches Typsystem bedeutet, dass Variablen keinen Typen haben. Sondern der Wert, den Typ. Du kannst dir die Variable vorstellen wie eine Box, die etwas Unbekanntes enthalten kann, wenn du die geschlossene Box öffnest und den Inhalt prüfst, kannst du den Typen feststellen, zum Beispiel „Holzspielzeug“ oder „Laptop“.
Hingegen ist C++ etwa eine Sprache mit statischem Typsystem, das bedeutet, dass Variablen einen Typen zugewiesen bekommen und dieser sich nicht mehr verändern werden kann. Um auf das Beispiel der Box zurückzukommen, wenn die Box gebaut wird, bekommt sie einen Typen „Holzspielzeug“ danach kann nur noch Holzspielzeug in die Kiste. Wodurch du niemals prüfen musst, was da gerade drinnen ist, da es nur von einem Typen sein kann.
Häufig kommen TypeGuards in Kombination mit TypeScript zum Einsatz. Diese hat den Hintergrund, dass TypeScript ein Superset von JavaScript ist, das für den Einsatz von Typen optimiert ist, allerdings werden die Typen von TypeScript nicht zur Laufzeit der Anwendung geprüft. Sondern sind eine Unterstützung für den Programmierer, um ein besseres Liniting anzubieten.
Um es einfach auszudrücken, ein Linter ist dafür da den Code zu analysieren und zu validieren, während er geschrieben wird, um so die Fehler zur Laufzeit der Anwendung zu reduzieren. Zusätzlich muss der Quelltext für den Client (Browser, NodeJS, etc.) wieder in JavaScript übersetzt werden, dieser Prozess heißt Transpilierung. Dadurch, dass TypeScript auf den Einsatz von Typen setz, werden hier TypeGuards häufiger benötigt als in einem regulären JavaScript-Code. Da der Linter bereits bei der Codeanalyse zweifle, haben kann, ob eine Variable von einem bestimmten Wert ist, worauf hin man in einigen Fällen einen TypeGuards schreiben kann, um das Problem zu lösen.
Ein Beispiel dafür könnte folgendermaßen aussehen, aus dem linken TypeScript Code wird nach der Übersetzung der rechte JavaScript-Code.
function add(a: number, b: number): number{
return a + b;
}
function add(a, b) {
return a + b;
}
Das Problem, warum wir hier TypeGuards ggf. benötigen, ist, dass wir für a und b zur Laufzeit wieder jeglichen Wert hineingeben könnten, mit unerwünschten Ergebnissen. Im Fall, dass es eine interne Funktion ist, die nicht von externen genutzt wird oder keine APIs benutzt werden, um dort Wert zu übergeben, braucht es in der Regel keine TypeGuards. Weil durch die Logik keine Fehler auftreten können.
Um es vereinfacht zu sagen, immer dann, wenn wir den Daten, die wir bekommen, nicht vertrauen können, dann ist der Zeitpunkt gekommen einen TypeGuard zu verwenden, um die Sicherheit unserer Anwendung zu verbessern.
Es ist möglich, einen einfachen TypeGuard für diese Situation, in der die Werte nicht bekannt sind, mit dem Keyword typeof
zu realisieren. Für primitive Datentypen eignet sich diese Variante. Ob wir hier die Typen verwenden möchten, also mit TypeScript oder ohne, ist für das Beispiel selbst nicht wichtig, da die Typen über JavaScript zur Laufzeit geprüft werden.
function isNumber(a) {
return typeof a === 'number';
}
function add(a, b){
if (!isNumber(a) || !isNumber(b)) {
return NaN;
}
return a + b;
}
Der Vollständigkeit halber folgende Typen kannst du mit typeof
prüfen string, number, bigint, boolean, symbol und object.
Aufpassen muss man hier bei den Werten NaN
und null.
NaN
(Not a Number) ist vom Typ number
und der Wert null
ist vom Typ object
. Diese sollte man immer beachten, wenn man TypeGuards schreibt.
Eine TypeGuard ist also ein Konstrukt von Abfragen, um sicherzustellen, dass nur die richtigen Werte an eine Methode oder Funktion übergeben werden können oder andernfalls wird ein Fehler geworfen oder ein bestimmter gewünschter Wert zurückgegeben.
Nachdem du kennengelernt hast, was ein TypeGuard an sich sein kann, und warum das ganze in JavaScript wichtig sein kann, will ich dir verschiedene Methode, Funktionen und Keywords vorstellen, mit denen du eine Typenüberprüfung bewerkstelligen kannst.
Das Keyword instanceof
kann genutzt werden, um zu prüfen, ob ein Objekt von einem bestimmten Prototyp abstammt. Der Prototyp ist ein Objekt eines Objekts, das die Methoden und Eigenschaften definiert.
Ein einfaches Beispiel der Verwendung wir bekommen einen Wert und müssen prüfen, ob es von der Date-Klasse ist, da wir danach mit den Methoden der Date-Klasse Arbeiten wollen.
const date = new Date();
if(date instanceof Date) {
console.log('date is a Date')
}
Intern passiert Folgendes, es wird von dem Wert in der Variable das Prototyp-Objekt mit dem Prototyp Objekt von Date verglichen, aber inklusive der Vererbungen bedeutet, wenn wir eine Klasse Hund hätten, die von Tier erbt. Dann wäre der Hund auch von der Instanz ein Hund.
Als Funktion ausgedrückt würde intern Folgendes passieren, es wird geprüft, ob das aktuelle den passenden Prototyp ist, falls nicht, wird geprüft, ob das Elternelement vom passenden Prototyp ist, bis kein Elternelement mehr vorhanden ist.
function isInstanceOf(obj, constructor) {
let currentObj = obj;
while (currentObj != null) {
if (currentObj === constructor.prototype) {
return true;
}
currentObj = Object.getPrototypeOf(currentObj);
}
return false;
}
Der „in“ Operator eignend sich vorwiegend dann, wenn man Objekte von verschiedenen Typen hat oder nur ein spezifischer Wert benötigt. Angenommen wir bekommen ein JSON von einer REST API und benötigen immer den Wert „currentAmount“ in unsere Funktion, dann könnten wir folgendes machen.
function getCurrentAmount(response){
if("currentAmount" in response){
return response.currentAmount;
}
return 0;
}
Wichtig zu wissen ist, dass in diesem Fall es egal ist, ob „currentAmount“ eine Eigenschaft vom Objekt selbst ist oder es nur Vererbt wurde. In diesen Fällen, dass man sicherstellen, will das, es definitiv ein Wert von „response“ ist, gibt es noch eine bessere Variante.
Die Variante hasOwnProperty
prüft, ob das Objekt selbst die Eigenschaft hat oder nur von einem Prototyp vererbt wurde, im Fall, dass es true
ist, können wir sichergehen, dass die Eigenschaft auf dem Objekt selbst vorhanden ist.
function getCurrentAmount(response){
if(response.hasOwnProperty('currentAmount')){
return response.currentAmount
}
return 0
}
Wichtig ist das sichergestellt wird das „response“ ein Objekt ist, ansonsten kann es zu einem Fehler kommen, da die Methode hasOwnProperty
nur auf Objekten existiert. Weshalb eine eigene Funktion zur Prüfung sinnvoll sein kann, um diese zu gewährleisten. Der Sonderfall von „null“ sollte hier bedacht werden.
function isObject(value) {
return value !== null && typeof value === 'object'
}
Ein Tagged Union trifft man vorwiegend in Kombination mit TypeScript an, dort gibt es sogenannte Union Typen, diese ist ein Typ, der von verschiedenen Arten sein kann wie Quadrat oder Kreis. Die Objekte müssen dabei eine Gemeinsamkeit haben, ein Tag, das sie eindeutig differenziert, um sie zur Laufzeit auseinanderzuhalten.
Zum Beispiel in dem Fall von Kreis und Quadrat könnte das ebendiese Art sein. Bei der Berechnung der Fläche benötigen wir ja genau diese Information, um zu bestimmen, welche Formel wir benötigen. So können wir eine Funktion nutzen, mit verschieden Objekten zur Laufzeit.
type Form =
| { art: 'kreis', radius: number }
| { art: 'quadrat', seitenLaenge: number };
function berechneFlaeche(form: Form) {
switch (form.art) {
case 'kreis':
return Math.PI * form.radius ** 2;
case 'quadrat':
return form.seitenLaenge ** 2;
}
}
const meinKreis: Form = { art: 'kreis', radius: 10 };
console.log(berechneFlaeche(meinKreis)); // Ausgabe (Gerundet): 314.16
const meinQuadrat: Form = { art: 'quadrat', seitenLaenge: 5 };
console.log(berechneFlaeche(meinQuadrat)); // Ausgabe: 25
Diese war nur ein Ausblick auf die internen Funktionen & Methoden, die JavaScript bereitstellt, um eigene TypeGuards zu definieren und Typen zu überprüfen.
Die Komplexität lässt sich in Zusammenhang mit eigenen Typen noch deutlich steigern, etwa die Prüfung von MD5-Strings, UUIDv4-Strings, Datenformaten und Uhrzeiten.
Dafür können dann eigene Funktionen entwickelt werden wie isUUID
oder isDate
. Die TypeGuards sind letztlich nur ein Konzept, das dir den Gedanken von Typ-Überprüfungen näher bringt.
Hinterlasse mir gerne einen Kommentar zum Artikel und wie er dir weitergeholfen hat beziehungsweise, was dir helfen würde das Thema besser zu verstehen. Oder hast du einen Fehler entdeckt, den ich korrigieren sollte? Schreibe mir auch dazu gerne ein Feedback!
Es sind noch keine Kommentare vorhanden? Sei der/die Erste und verfasse einen Kommentar zum Artikel "Wie TypeGuards dein JavaScript & TypeScript Code verbessern können!"!