TypeScript 2.0: Neuheiten und erweiterte Features

12. Oktober 2016

TypeScript ist eine typsichere objektorientierte Programmiersprache aus dem Hause Microsoft, welche im Jahr 2012 erstmals veröffentlich wurde und seitdem eine permanente Weiterentwicklung erfährt. Ziel dieses Flurfunkartikels ist es, einige neue Features aus TypeScript 2.0 vorzustellen, dessen Beta-Version im Juni dieses Jahres für Visual Studio 2015 zur Verfügung gestellt wurde und am 22. September als neue fertige Release-Version erscheinen ist.

Non-nullable und non-undefined Types

In JavaScript haben die speziellen Datentypen  bzw. Werte undefined und null eine essentielle Bedeutung. Der Typ ‘undefined’ ist in JavaScript sowohl ein eigener Datentyp als auch der (Default-)Wert für Variablen, denen aus irgendwelchen Gründen (noch) kein Wert zugewiesen wurde. Bei dem Keyword ‘null’ handelt es sich dagegen um keinen eigenständigen Datentypen sondern nur um eine mögliche Wertausprägung vom Typ object.

Vor TypeScript-Version 2.0 konnte bislang nicht ausgeschlossen werden, dass eine Variable oder ein Funktionsparameter den Wert ‘undefined’ oder ‘null’ besitzen. Zuweisungen wie im nachfolgenden Code-Snippet waren daher durchaus gültig:

   1: // vor TypeScript 2.0

   2: let varNumber: number;

   3: varNumber = undefined;

   4: varNumber = null;

   5: varNumber = 73;

Die Folge dieser schwachen Typisierung war, dass sehr viele Prüfungen auf ‘null’ bzw. ‘undefined’ durchgeführt werden mussten, ehe die eigentliche Implementierungslogik umgesetzt werden konnte. Das in TypeScript 2.0 neu eingeführte strictNullChecks-Flag, welches beim Kompilierungsvorgang mit angegeben werden kann, wirkt diesem Problem nun entgegen und sorgt dafür, dass eine strengere Typisierung gewährleistet wird. Sobald versucht wird einen sogenannten non-nullable bzw. non-undefined Typ mit Wert ‘null’ oder ‘undefined’ zu versehen, liefert der Compiler eine entsprechende Fehlermeldung. Dieses Verhalten wird durch folgende Abbildung grafisch belegt:

TypeScript strictNullChecks

Unnötige Checks auf die meistens unerwünschten Werte ‘null’ oder ‘undefined’ können somit entfallen. Der Code wird einfacher, übersichtlicher und somit vermutlich auch wartbarer. Die Codezeilen des folgenden Code-Snippets zeigen – die Verwendung des stricktNullChecks-Flags vorausgesetzt – noch einige weitere Beispiele gültiger und ungültiger Zuweisungsoperationen im Zusammenhang mit numerischen Werten, ‘null’ und ‘undefined’:

   1: // Compiled with --numberictNullChecks

   2: let numberA: number;

   3: let numberB: number | undefined;

   4: let numberC: number | null | undefined;

   5: numberA = 73;  // valid

   6: numberB = 73;  // valid

   7: numberC = 73;  // valid

   8: numberA = undefined;  // invalid

   9: numberB = undefined;  // valid

  10: numberC = undefined;  // valid

  11: numberA = null;  // invalid

  12: numberB = null;  // invalid

  13: numberC = null;  // valid

  14: numberA = numberB;  // invalid

  15: numberA = numberC;  // invalid

  16: numberB = numberA;  // valid

  17: numberB = numberC;  // invalid

  18: numberC = numberA;  // valid

  19: numberC = numberB;  // valid

.

Der Typ ‘never’ in TypeScript 2.0

Viele Programmiersprachen befolgen Prinzipien der Typentheorie, einer mathematischen Disziplin, welche besagt, dass sämtliche Programmieranweisungen, Terme oder Funktionen auf bestimmte Typen beschränkt sind. Daher besitzen viele Programmiersprachen in ihren zulässigen Menge an Datentypen einen sogenannten Bottom Type, welcher keinen konkreten Wert annehmen kann.

Nachdem durch die Einführung des strictNullChecks-Flags aus dem vorherigen Abschnitt des Artikels null und undefinied zu konkreten Datentypen von TypeScript “befördert” wurden, bedarf es eines neuen Datentyps, welcher die Rolle des Falsums – ein gelegentlich verwendetes Synonym für Bottom Type – übernimmt. Hierfür wurde in TypeScript 2.0 der neue (primitive) Datentyp never eingeführt, welcher keine konkreten Werteausprägungen besitzt und folgenden Eigenschaften vorweist:

  • der Typ ‘never’ ist ein Sub-Typ und kann jedem anderen Typen zugewiesen werden (z.B. let n: number = never)
  • kein anderer Sub-Typ kann ‘never’ zugewiesen werden, außer ‘never’ selbst
  • besitzt eine Funktion einen expliziten never-Rückgabewert, so müssen entweder alle return-Statements vom Typ ‘never’ sein oder das Ende der Funktion darf nicht erreichbar sein

Der Datentyp ‘never’ besitzt vermutlich vorrangig theoretische Existenzberechtigungen, um das Typsystem von TypeScript konsistent zu halten. Er sollte auch nicht mit dem void-Operator verwechselt werden, welcher bei Funktionen zwar nichts(!) zurückliefert dafür aber immer die Endpunkte einer Funktion erreicht.

Use-Cases, bei denen der Datentyp ‘never’ tatsächlich auftreten kann, existieren auch und sind beispielsweise folgend Fälle:

  • eine Funktion terminiert niemals (z.B. Endlosschleife)
  • eine Funktion besitzt unerreichbare Code-Abschnitte
  • eine Funktion erzeugt ausschließlich Fehler bzw. Error-Objekte

Daher stellen die folgenden Code-Beispiele gültige Szenarien für den Einsatz des never-Datentyps dar:

   1: function infinityLoop() : never {

   2:     while(true) {

   3:     }

   4:     // unreachable code

   5: }

   6:

   7: function BoolToYesOrNo(value: boolean): never {

   8:     if (value) {

   9:         return "Yes";

  10:     }

  11:     else if (value == false) {

  12:         return "No"

  13:     }

  14:     // unreachable code

  15: }

  16:

  17: function alwaysFails(message: string) {

  18:     throw new Error(message); // implicit use of never type

  19: }

..

Private und protected Konstruktoren

In älteren TypeScript-Versionen ist es nicht möglich, den Konstruktor einer Klasse mit den Schlüsselwort private oder protected zu versehen. Beim Versuch wurde, wie die folgende Abbildung zeigt, vom Compiler immer eine entsprechende Fehlermeldung zurückgegeben:

Compilerfehler für private Konstrzuktor (vor TypeScript 2.0)

Mit TypeScript 2.0 besteht nun die Möglichkeit Konstruktoren mit Private- oder Protected-Modifier hinsichtlich ihrer Sichtbarkeit zu modifizieren. Besitzt eine Klasse einen Private-Konstruktor, so können außerhalb der Klasse weder Instanzen von dieser angelegt werden noch kann der Konstruktor in ableitenden Klassen verwendet werden. Insbesondere bei Realisierung des Singleton-Patterns ist dies ein gängiges Konzept. Klassen mit einem Protected-Konstruktor können über diesen ebenfalls nicht direkt instanziiert werden, allerdings ist es möglich auf diesen in ableitenden Klassen darauf zuzugreifen.

Readonly-Keyword

Als nächstes neues Feature hat das Keyword readonly Einzug in TypeScript 2.0 gehalten. Mit diesem wird sicher gestellt, dass Felder nur einmalig mit einem Wert versehen werden. Weitere Änderungen an readonly-Feldern, sei es durch Funktionsaufrufe oder Property-Zugriffe, sind nicht möglich.

TypeScripts neuste Version orientiert sich hierbei an C#, Microsofts Zugpferd was Programmiersprachen betrifft. Die Initialisierung kann, wie im folgenden Code-Ausschnitt dargelegt, entweder direkt nach der Deklaration des Feldes oder innerhalb des Konstruktor erfolgen:

   1: class GeometricFigure {

   2:

   3:     private readonly _createdAt: Date = new Date(); // declaration and initialization

   4:

   5:     private readonly _name: string; // declaration

   6:

   7:     constructor(name: string) {

   8:         this._name = name; // initialization in constructor

   9:     }

  10:

  11:     get name(): string {

  12:         return this._name;

  13:     }

  14:

  15:     get createdAt() {

  16:         return this._createdAt;

  17:     }

  18:

  19:     get name(): string {

  20:         return this._name;

  21:     }

  22: }

..

Abstract-Properties

In einem früheren SDX-Flurfunkbeitrag, welcher den Titel „abstract“ in TypeScript trägt, wurde das abstract-Keyword in der TypeScript-Version 1.8 bereits ausführlich analysiert. Damals beschränkte sich dessen Verwendung noch auf Klassen und Methoden.

Mit Version 2.0 entfällt diese Restriktion nun. Neben Klassen und Methoden können fortan auch Propertys einer Klasse mit dem abstract-Keyword ausgestattet und ähnlich wie in C# realisiert werden. Dabei muss jede konkrete Unterklasse das abstrakte Property der abstrakten Oberklasse mit einem Wert initialisieren. Ein Code-Snippet, welches sich an den Beispielen des vorangegangen Artikels angelehnt ist, sieht wie folgt aus:

   1: abstract class GeometricFigure {

   2:

   3:     private readonly _createdAt: Date = new Date();

   4:

   5:     private readonly _name: string;

   6:

   7:     constructor(name: string) {

   8:         this._name = name;

   9:     }

  10:

  11:     get createdAt() {

  12:         return this._createdAt;

  13:     }

  14:

  15:     get name(): string {

  16:         return this._name;

  17:     }

  18:

  19:     abstract get area(): number; // abstract getter 

  20:

  21:     abstract set area(v: number); // abstract setter

  22: }

  23:

  24: class Circle extends GeometricFigure {

  25:

  26:     private _radius: number;

  27:

  28:     constructor(name: string, radius: number) {

  29:         super(name);

  30:         this._radius = Math.max(radius, 0);

  31:     }

  32:

  33:     area = Math.pow(this._radius, 2) * Math.PI; // initialization for circle area

  34: }

  35:

  36: class Rectangle extends GeometricFigure {

  37:

  38:     private _height: number;

  39:     private _width: number;

  40:

  41:     constructor(name: string, width: number, height: number) {

  42:         super(name);

  43:         this._height = Math.max(height, 0);

  44:         this._width = Math.max(width, 0);

  45:     }

  46:

  47:     area = this._width * this._height; // initialization for rectangle area

  48: } 

 .

Fazit

Alle in diesem Artikel vorgestellten TypeScript 2.0-Features stellen interessante und vermutlich auch sehr nützliche Erweiterungen dar. Wichtige objektorientierte Konzepte wie Datenkapselung oder das Geheimnisprinzip können nun noch intensiver in Web-Applikationen umgesetzt werden. Des Weiteren dürfte die Einführung des strictNullChecks-Flags besonders bei Sympathistanten typsicherer Programmiersprachen großen Anklang finden.

Der Einsatz der OOP-Sprache TypeScript stößt in Entwicklerkreise allgemein oftmals auf geteiltes Echo, das belegt beispielsweise folgende Forums-Diskussion auf der heise-Webseite. Während es für die einen nichts weiter als “Syntactic Sugar” für JavaScript ist, sehen andere – insbesondere Anhänger der objektorientierten Programmierung – in TypeScript eine Erleichterung hinsichtlich Strukturierung großer und komplexer Web-(Enterprise-)Applikationen.

Egal welche Einstellung nun überwiegen mag – die Wahrheit liegt wie so oft vermutlich irgendwo in der Mitte – eine Tatsache lässt sich nicht leugnen: TypeScript lebt und seine Anhängerschaft wächst! Dazu genügt allein ein Blick auf die Roadmap, welche sich schon mit einer Version 2.1 befasst.

Quellen