Einführung in TypeScript: Setup und Typsystem

Keine Kommentare

Seitdem das Angular-Team verkündet hat, dass der AngularJS-Nachfolger Angular2 in TypeScript entwickelt wird, ist TypeScript mehr in den Fokus gerückt. Dabei kann TypeScript auch ohne Angular2 überzeugen und findet mehr und mehr Nutzer. Grund genug, hier im Blog eine kleine Einführung in die Arbeit mit TypeScript zu geben. In den kommenden Wochen zeige ich, wie man ein Projekt mit TypeScript aufsetzt, welche Features TypeScript uns bietet und auch, wie man TypeScript in seine bestehende AngularJS-Anwendung integrieren kann.

Heute stelle ich ein minimales Setup vor und gebe einen Einblick in das Typsystem von TypeScript.

TypeScript

Die Verwendung von TypeScript ist keine Entscheidung gegen JavaScript. Sie folgt eher dem aktuellen Trend, JavaScript mithilfe von Transpilern zu generieren, um mit moderneren Sprachfeatures arbeiten zu können. TypeScript selbst ist ein Superset von JavaScript und bietet primär Funktionalität an, die entweder in einer zukünftigen Version von ECMAScript Standard wird (z.B. Arrow-Funktionen) oder aber syntaktischen Zucker für bestehende Funktionalitäten bietet. Im Projektalltag erleichtert das statische Typsystem die Arbeit an komplexen Anwendungen enorm. Selbiges ermöglicht auch einen sehr guten IDE-Support. Ich gehe hier nicht so weit wie André Staltz in seinem Blog, der fordert, dass alle Bibliotheken in TypeScript geschrieben werden sollten, möchte jedem Entwickler aber einen Blick auf TypeScript ans Herz legen.

Im Folgenden möchte ich einige der Eigenschaften von TypeScript vorstellen. Eine wesentlich gründlichere Einführung gibt es auf der Projektseite oder auch im Guide von Basarat Ali Syed.

Zunächst ist wichtig, dass TypeScript ein Superset von JavaScript ist, und auch, wenn es Fehler anzeigt, immer in JavaScript umgewandelt wird. In der Theorie ist also eine einfache Umbenennung der .js-Dateien in .ts-Dateien schon die halbe Miete. Die Erfahrung zeigt aber, dass es einfacher ist, diese Migration Schritt für Schritt durchzuführen und zunächst neue Funktionalität in TypeScript zu entwickeln und alten Code, wo notwendig, zu überarbeiten.

Minimales Setup

Wir werden uns in einem späteren Teil ansehen, wie man TypeScript in seinen Build integrieren kann. Bis dahin reicht für die ersten Schritte ein einfacheres Setup. Damit alles funktioniert, sollten aktuelle Versionen von Node.js und npm installiert sein. TypeScript kann dann mit dem folgenden Befehl global installiert werden:

npm install -g typescript

Warum global? Wenn wir TypeScript global installieren, können wir den TypeScript Compiler einfach via tsc ausführen. Man kann diesen natürlich auch nur lokal installieren, dann ist das aber etwas umständlicher.

Ein tsc -v sollte nun eine Version >= 1.8.9 ausgeben.

Jetzt fehlt nur noch ein Ordner, in dem entwickelt wird (z.B. hello-typescript). In diesem legen wir zunächst einen Ordner src für den Quellcode an und eine Datei tsconfig.json, die die Konfiguration für den TypeScript Compiler enthält. Im Ordner src legen wir eine Datei app.ts für unseren Code an.

Die Ordnerstruktur sieht danach so aus:

- hello-typescript/
   |
   |- src/
   |   |
   |   |- app.ts
   |
   |- tsconfig.json

In die tsconfig.json schreiben wir die folgende Konfiguration:

{
  "compilerOptions": {
    "target": "es5"
  }
}

Diese Konfiguration stellt sicher, dass der TypeScript Code in ES5 kompiliert wird. In vielen IDEs reicht die tsconfig.json auch schon aus, damit der Code automatisch kompiliert wird.

Ein einfaches Beispiel

Zunächst schreiben wir folgenden Code in app.ts:

function add(x, y) {
  return x + y;
}

var result = add(1, 2);
console.log(result);

Im Ordner hello-typescript führen wir dann tsc -p . aus, um den TypeScript-Code in JavaScript zu kompilieren (-p . stellt sicher, dass die lokale tsconfig.json verwendet wird). Mit node src/app.js führen wir den generierten Code aus (natürlich könnte man die JavaScript-Datei auch einfach über den Browser in ein HTML-Dokument integrieren). Im Terminal erscheint nun die Zahl 3.

Im Ordner src findet man nun die Datei app.js, die den kompilierten Code anzeigt. Was direkt auffällt: Es gibt so gut wie keinen Unterschied zu unserer TypeScript-Datei (nur ein paar Leerzeilen fehlen). Da wir hier aber eigentlich nur JavaScript geschrieben haben, wäre alles andere überraschend.

Typsystem

Wie der Name schon ahnen lässt, bietet TypeScript im Gegensatz zu JavaScript ein statisches Typsystem. Die Vorteile liegen auf der Hand:

  • viele Fehler fallen schon auf, wenn der Code kompiliert wird
  • Refactoring des Codes ist wesentlich einfacher
  • Typen sind eine gute Form der Dokumentation

Warum funktioniert unser Beispiel nun ohne die Angaben von Typen? Der Grund hierfür ist, dass TypeScript implizite Typen unterstützt. Natürlich können wir hier auch etwas konkreter werden und die Signatur unserer Funktion anpassen:

function add(x: number, y: number): number {
  return x + y;
}

var result = add(1, 2);

console.log(result);

Die Funktion add erwartet nun also zwei Parameter vom Typ number und gibt ebenfalls einen Wert vom Typ number zurück. Wenn wir nun diesen Code wieder kompilieren und ausführen, ist das Ergebnis identisch. Das besonders hier: Die JavaScript-Datei hat sich nicht verändert: TypeScript entfernt alle Typinformationen.

Gut, provozieren wir einen Fehler:

var result = add(1, 'foo');

Je nach IDE wird dieser Code nicht automatisch kompiliert. Wenn wir dies aber nun manuell mit tsc tun, passieren zwei Dinge:

  • wir bekommen eine Fehlermeldung error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
  • es wird trotzdem eine JavaScript-Datei erzeugt (wenn wir diese kompilieren und ausführen, bekommen wir als Ausgabe 1foo).

Wenn wir verhindern wollen, dass bei einem Fehler JavaScript erzeugt wird, können wir in der tsconfig.json einfach das Property noEmitOnError auf true setzen.

Gerade, wenn man bestehenden Code migiriert, kommt es schnell vor, dass man einer Variable keinen Typ zuweisen kann (beispielsweise weil es noch keine Typdefinition für einen Typ gibt). In diesem Fall kann man einer Variable auch den Typ any geben.

Interfaces

Bauen wir unser Beispiel ein wenig aus und addieren komplexe Zahlen:

interface IComplexNumber {
    real: number;
    imaginary: number;
}

function add(x: IComplexNumber, y: IComplexNumber): IComplexNumber {
    return {
        real: x.real + y.real,
        imaginary: x.imaginary + y.imaginary
    }
}

var a = {
    real: 1,
    imaginary: 2
};

var b = {
    real: 3,
    imaginary: 4
};

var result = add(a, b);

console.log(result);

Wir definieren komplexe Zahlen zunächst über ein Interface IComplexNumber. Die Funktion add akzeptiert nun zwei komplexe Zahlen, addiert diese und gibt eine neue komplexe Zahl zurück. So weit, so gut.

Wenn wir diesen Code kompilieren und ausführen, lautet das Ergebnis { real: 4, imaginary: 6 }. Wie auch bei den anderen Beispielen entfernt der Compiler alle Typinformationen.

Interessant ist die Initialisierung der Werte: Wir müssen hier keinen Konstruktor verwenden, sondern können einfach ein Objekt erzeugen, das dem Interface entspricht. Wir können hier auch zusätzliche Felder definieren, was in diesem Use Case aber keinen Sinn ergibt. TypeScript berücksichtigt also die Struktur der Objekte (structural typing).

var b = {
    real: 3,
    imaginary: 4,
    foo: 5
};

Das war es auch schon für heute. In diesem Teil haben wir TypeScript installiert, ein Projekt aufgesetzt und uns das Typsystem von TypeScript angesehen. Im nächsten Teil werfen wir einen Blick auf Klassen und Interfaces in TypeScript.

Wie sehen Eure Erfahrungen mit TypeScript aus? Habt ihr es bereits im Projekt eingesetzt oder denkt darüber nach?

Daniel Mies

Daniel ist als Entwickler und IT Consultant sowohl im Backend als auch im Frontend unterwegs. Er interessiert sich besonders für Reaktive Programmierung und hier speziell Scala und Akka.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *