Lerne Coding
Wie TypeGuards dein JavaScript & TypeScript Code verbessern können!

Die Wächter der Typen in JavaScript & TypeScript

Inhaltsverzeichnis
[[TABLE OF CONTENTS]]
access_timeGeschätzte Lesezeit ca. Minuten

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.

Was ist ein dynamisches & statisches Typsystem?

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.

Was sind TypeGuards?

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.

TypeScript
function add(a: number, b: number): number{
  return a + b;
}
JavaScript
function add(a, b) {
    return a + b;
}

Wann sind TypeGuards wichtig?

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.

Wie kann ein TypeGuard aussehen?

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.

Verschiedene Varianten von TypeGuards in JavaScript

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.

TypeGuards mittels instanceof

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

Objekte prüfen mittels in Operator

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.

Prüfen von Objekten mittels hasOwnProperty Methode

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'
}

Tagged Unions in Kombination mit TypeScript

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

Fazit

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.

Kommentare zum Artikel

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!"!

Kommentar schreiben

Vom Autor Empfohlen
close