Einführung
TypeScript erweitert JavaScript um statische Typisierung und objektorientierte Features, die den Entwicklungsprozess robuster und effizienter gestalten. Als Superset von JavaScript bietet TypeScript fortschrittliche Werkzeuge für Fehlerprüfung zur Kompilierungszeit, verbesserte IDE-Unterstützung und umfassende Dokumentationsmöglichkeiten. Diese Grundlagen-Einführung behandelt die wesentlichen Konzepte, Syntax und Vorteile der typsicheren Entwicklung mit TypeScript.
Inhaltsverzeichnis
Entstehung und Philosophie
TypeScript wurde 2012 von Microsoft veröffentlicht, um ein fundamentales Problem von JavaScript zu lösen: Das Fehlen eines statischen Typsystems. JavaScript wurde ursprünglich für kleine Scripte in Webseiten entwickelt, wird heute aber für komplexe Anwendungen mit Millionen von Codezeilen verwendet. Diese Evolution brachte Herausforderungen mit sich:
- Laufzeitfehler: Typfehler werden erst entdeckt, wenn der Code ausgeführt wird.
- Schlechte Tooling-Unterstützung: IDEs können ohne Typinformationen nur begrenzt helfen.
- Schwierige Refactorings: Große Codeänderungen sind riskant ohne Compiler-Unterstützung.
- Unklare APIs: Funktionssignaturen geben keine Hinweise auf erwartete Datentypen.
TypeScript löst diese Probleme durch ein optionales, graduelles Typsystem.
Das bedeutet:
- Optional: Man kan TypeScript schrittweise einführen. Nicht jede Variable muss typisiert werden.
- Graduell: Man kann mit JavaScript beginnen und nach und nach Typen hinzufügen.
- Strukturell: TypeScript verwendet “Duck Typing” - wenn es wie eine Ente aussieht und quakt, ist es eine Ente.
Funktionsweise
TypeScript ist technisch gesehen ein Transpiler (Source-to-Source Compiler). Der TypeScript-Code wird in JavaScript kompiliert, das dann in jedem JavaScript-Runtime läuft.
- TypeScript Code (.ts)
- TypeScript Compiler (tsc)
- JavaScript Code (.js)
- JavaScript Runtime
Wichtige Konzepte
Compile-Time vs. Runtime
- TypeScript existiert nur zur Compile-Zeit
- Alle Typ-Informationen werden beim Kompilieren entfernt (“Type Erasure”)
- Der resultierende JavaScript-Code enthält keine Typ-Informationen
- Typen haben keinen Einfluß auf die Laufzeit-Performance
Superset-Prinzip
- Jeder gültige JavaScript-Code ist auch gültiger TypeScript-Code
- TypeScript fügt JavaScript neue Syntax hinzu, ändert aber nicht die Semantik
- Man kann
.js
Dateien einfach in.ts
umbenennen als Startpunkt
Strukturelle Typisierung
// TypeScript kümmert sich um die
// Struktur, nicht um Namen
interface Point {
x: number;
y: number;
}
// Diese Klasse implementiert Point
// implizit durch ihre Struktur
class Coordinate {
constructor(
public x: number,
public y: number
) {}
}
function printPoint(p: Point) {
console.log(`(${p.x}, ${p.y})`);
}
// Funktioniert, obwohl Coordinate Point
// nicht explizit implementiert wurde
printPoint(new Coordinate(10, 20));
Vorteile von TypeScript
Früherkennung von Fehlern
In JavaScript werden viele Fehler erst zur Laufzeit entdeckt.
Im folgenden JavaScript-Beispiel wird der Fehler erst bei der Ausführung wirklich sichtbar.
function calculatePrice(price, quantity) {
return price * quantity;
}
calculatePrice("Art-Name", 3);
Hier würde man als Ergebnis ein NaN
erhalten. Allerdings erst, wenn man dieses Script ausführen würde.
TypeScript erkennt solche Fehler während der Entwicklung. Hier wurden die Typen für die Parameter hinzugefügt. Somit weiß nun die IDE, dass price
nur vom Typ number
(Zahl) sein kann.
function calculatePrice(price: number, quantity: number) {
return price * quantity;
}
calculatePrice("Art-Name", 3); // Das Argument vom Typ "string" kann dem Parameter vom Typ "number" nicht zugewiesen werden.
Verbesserte IDE-Unterstützung
TypeScript ermöglicht IDEs, intelligente Features anzubieten:
- Autocompletion: Die IDE erkennt alle verfügbaren Properties und Methoden
- Refactoring: Sichere Umbenennungen über die gesamte Codebase
- Navigation: Man kann zur Definition springen und alle Referenzen finden
- Inline-Dokumentation: Typ-Informationen als Dokumentation
Selbstdokumentierender Code
Typen fungieren als lebende Dokumentation.
function processUser(user, options) {
// ...
}
Hier ist nicht klar, was diese Funktion erwartet.
Mit Typen sind die Erwartungen klar.
interface User {
id: string;
name: string;
email: string;
}
interface ProcessOptions {
sendEmail?: boolean;
updateLastLogin?: boolean;
}
function processUser(user: User, options: ProcessOptions): void {
// ...
}
Refactoring-Unterstützung
Bei großen Codebasen ist Refactoring in JavaScript riskant. TypeScript macht es einfacher.
// Änderung einer Property
interface Product {
id: string;
// name: string;
title: string;
price: number;
}
// TypeScript zeigt Fehler an allen Stellen,
// die noch 'name' verwenden
Installation und Setup
TypeScript kann auf verschiedene Arten verwendet werden.
Globale Installation
Um TypeScript global auf dem System zu installieren, wird NodeJS und NPM benötigt.
npm install -g typescript
Projekt-Setup
Neues Projekt mit TypeScript initialisieren.
mkdir my_project
cd my_project
npm init -y
Anschließend TypeScript als Entwicklungsabhängigkeit installieren. Da TypeScript zu JavaScript kompiliert wird, wird das Paket “TypeScript” nur während der Entwicklung benötigt.
npm install --save-dev typescript
TypeScript-Konfiguration erstellen
npx tsc --init
Grundlegende Konfiguration
Die Konfiguration für TypeScript wird in der Regel in einer tsconfig.json
festgehalten. Die tsconfig.json
ist das Herzstück jedes TypeScript-Projekts.
Diese Datei (Konfiguration) definiert:
- Welche Dateien kompiliert werden sollen
- Welche Compiler-Optionen verwendet werden
- Wie strikt die Typ-Prüfung sein soll
Hier eine grundlegende Beispiel-Konfiguration.
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Die einzelnen Optionen werden in einem separaten Artikel erklärt.
TypeScript vs. JavaScript - Praktischer Vergleich
Betrachten wir ein realistisches Beispiel - eine Funktion zur Verarbeitung von Benutzerdaten.
function processUserData(users, options) {
if (!options.includeInactive) {
users = users.filter(u => u.active);
}
return users.map(user => {
id: user.id,
displayName: options.useFullname
? `${user.firstName} ${user.lastName}`
: user.firstName,
email: user.email.toLowerCase(),
lastLogin: new Date(user.lastLogin)
});
}
interface User {
id: string;
firstName: string;
lastName: string;
email: string;
active: boolean;
lastLogin: string | Date;
}
interface ProcessOptions {
includeInactive?: boolean;
useFullName?: boolean;
}
interface ProcessedUser {
id: string;
displayName: string;
email: string;
lastLogin: Date;
}
function processUserData(users: User[], options: ProcessOptions): ProcessedUser[] {
let filteredUsers = users;
if (!options.includeInactive) {
filteredUsers = users.filter(u => u.active);
}
return filteredUsers.map(user => {
id: user.id,
displayName: options.useFullName
? `${user.firstName} ${user.lastName}`
: user.firstName,
email: user.email.toLowerCase(),
lastLogin: user.lastLogin instanceof Date
? user.lastLogin
: new Date(user.lastLogin)
});
}
Die TypeScript-Version bietet:
- Klare Dokumentation der erwarteten Datenstrukturen
- Compile-Zeit-Validierung aller Zugriffe
- Autocompletion für alle Properties
- Sichere Refactorings