Markierte Typen in TypeScript

Beim Modellieren von Entitäten mit TypeScript ist es sehr üblich, eine Schnittstelle wie diese zu erhalten:

TypeScript

 

Das Problem

Die Typen der Eigenschaften haben keine semantische Bedeutung. In Bezug auf Typen sind User.id, Order.id, Order.year usw. gleich: eine Zahl, und als Zahl sind sie austauschbar, aber semantisch gesehen sind sie es nicht.

Nach dem vorherigen Beispiel können wir eine Reihe von Funktionen haben, die Aktionen über die Entitäten ausführen. Zum Beispiel:

TypeScript

 

Diese Funktionen akzeptieren jede Zahl in einem beliebigen Argument, unabhängig von der semantischen Bedeutung der Zahl. Zum Beispiel:

TypeScript

 

Offensichtlich ist das ein großer Fehler, und es könnte einfach erscheinen, den Code zu lesen zu vermeiden, aber der Code ist nicht immer so einfach wie das Beispiel.

Das Gleiche gilt für getOrdersFiltered: Wir können die Werte von Tag und Monat vertauschen, und es wird keine Warnung oder Fehlermeldung geben. Die Fehler treten auf, wenn der Tag größer als 12 ist, aber es ist offensichtlich, dass das Ergebnis nicht das Erwartete sein wird.

Die Lösung

Die Regeln der Objektkalisthenik bieten eine Lösung dafür: Verpacken Sie alle Primitivtypen und Strings (Verwandtes Primitive Obsession Anti-Pattern). Die Regel besagt, dass die Primitiven in einem Objekt verpackt werden sollen, das eine semantische Bedeutung darstellt (DDD beschreibt das als ValueObjects).

Aber mit TypeScript müssen wir dafür keine Klassen oder Objekte verwenden: Wir können das Typsystem verwenden, um sicherzustellen, dass eine Zahl, die etwas anderes als ein Jahr darstellt, nicht anstelle eines Jahres verwendet werden kann.

Markierte Typen

Dieses Muster nutzt die Erweiterbarkeit von Typen, um eine Eigenschaft hinzuzufügen, die die semantische Bedeutung sicherstellt:

TypeScript

 

Diese einfache Zeile erstellt einen neuen Typ, der als Zahl fungieren kann – aber keine Zahl ist, sondern ein Jahr.

TypeScript

 

Allgemeine Lösung

Um das Schreiben eines Typs pro Markentyp zu vermeiden, können wir einen Hilfstyp erstellen, der beispielsweise folgendermaßen aussieht:

TypeScript

 

Der ein eindeutiges Symbol als Markeneigenschaftsnamen verwendet, um Konflikte mit Ihren Eigenschaften zu vermeiden, und die ursprüngliche Typ- und Marken als generische Parameter erhält.

Damit können wir unsere Modelle und Funktionen wie folgt umstrukturieren:

TypeScript

 

Jetzt wird in diesem Beispiel der IDE ein Fehler angezeigt, da id ein UserId ist und deleteOrder einen OrderId erwartet.

TypeScript

 

Abwägungen

Als kleinen Kompromiss müssen Sie X als Brand verwenden. Zum Beispiel const year = 2012 as Year, wenn Sie einen neuen Wert aus einem primitiven Wert erstellen, aber dies entspricht einem new Year(2012), wenn Sie Wertobjekte verwenden. Sie können eine Funktion bereitstellen, die als eine Art „Konstruktor“ fungiert:

TypeScript

 

Validierung mit Markentypen

Markentypen sind auch nützlich, um sicherzustellen, dass die Daten gültig sind, da Sie spezifische Typen für validierte Daten haben können und Sie darauf vertrauen können, dass der Benutzer nur durch Verwendung von Typen validiert wurde:

TypeScript

 

Readonly ist nicht obligatorisch, aber um sicherzustellen, dass Ihr Code die Daten nach der Validierung nicht ändert, wird es sehr empfohlen.

Zusammenfassung

Markentypen sind eine einfache Lösung, die Folgendes umfasst:

  • Verbessert die Lesbarkeit des Codes: Macht deutlicher, welcher Wert in jedem Argument verwendet werden sollte
  • Zuverlässigkeit: Hilft, Fehler im Code zu vermeiden, die schwer zu erkennen sein können; jetzt hilft uns die IDE (und die Typüberprüfung), zu erkennen, ob der Wert am richtigen Platz ist
  • Datenvalidierung: Sie können gebrandete Typen verwenden, um sicherzustellen, dass die Daten gültig sind.

Sie können sich gebrandete Typen als eine Art Version von ValueObjects vorstellen, jedoch ohne Klassen zu verwenden – nur Typen und Funktionen.

Genießen Sie die Kraft der Typisierung!

Source:
https://dzone.com/articles/branded-types-in-typescript