TypeScript 5.0 wurde am 16. März 2023 offiziell veröffentlicht und steht nun für alle zur Verfügung. Diese Version führt viele neue Funktionen ein, mit dem Ziel, TypeScript kleiner, einfacher und schneller zu machen.

Die neue Version modernisiert die Dekoratoren für die Klassenanpassung und ermöglicht die wiederverwendbare Anpassung von Klassen und ihren Mitgliedern. Entwickler können jetzt einen const-Modifikator zu einer Typparameter-Deklaration hinzufügen, so dass const-ähnliche Inferenzen die Standardeinstellung sind. Die neue Version macht außerdem alle Enums zu Union Enums, was die Codestruktur vereinfacht und das TypeScript-Erlebnis beschleunigt.

In diesem Artikel lernst du die Änderungen in TypeScript 5.0 kennen und erhältst einen detaillierten Einblick in die neuen Funktionen und Möglichkeiten.

Erste Schritte mit TypeScript 5.0

TypeScript ist ein offizieller Compiler, den du mit npm in deinem Projekt installieren kannst. Wenn du TypeScript 5.0 in deinem Projekt verwenden möchtest, kannst du den folgenden Befehl im Verzeichnis deines Projekts ausführen:

npm install -D typescript

Dadurch wird der Compiler in das Verzeichnis node_modules installiert, das du nun mit dem Befehl npx tsc aufrufen kannst.

Eine Anleitung zur Verwendung der neueren Version von TypeScript in Visual Studio Code findest du auch in dieser Dokumentation.

Was ist neu in TypeScript 5.0?

In diesem Artikel stellen wir dir 5 wichtige Neuerungen in TypeScript vor. Zu diesen Funktionen gehören:

Modernisierte Dekoratoren

Decorators gibt es in TypeScript schon seit einiger Zeit unter einer experimentellen Flagge, aber die neue Version bringt sie auf den neuesten Stand des ECMAScript-Vorschlags, der sich jetzt in Phase 3 befindet, d.h. in einer Phase, in der er in TypeScript aufgenommen wird.

Decorators sind eine Möglichkeit, das Verhalten von Klassen und ihren Mitgliedern auf eine wiederverwendbare Weise anzupassen. Wenn du zum Beispiel eine Klasse hast, die zwei Methoden hat: greet und getAge:

class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }

    getAge() {
        console.log(`I am ${this.age} years old.`);
    }
}

const p = new Person('Ron', 30);
p.greet();
p.getAge();

In der Praxis sollte diese Klasse kompliziertere Methoden haben, die eine asynchrone Logik verarbeiten und Seiteneffekte haben, z. B. wenn du einige console.log Aufrufe einbauen möchtest, um die Methoden zu debuggen.

class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log('LOG: Method Execution Starts.');
        console.log(`Hello, my name is ${this.name}.`);
        console.log('LOG: Method Execution Ends.');
    }

    getAge() {
        console.log('LOG: Method Execution Starts.');
        console.log(`I am ${this.age} years old.`);
        console.log('Method Execution Ends.');
    }
}

const p = new Person('Ron', 30);
p.greet();
p.getAge();

Das ist ein häufig auftretendes Muster, und es wäre praktisch, eine Lösung zu haben, die auf jede Methode angewendet werden kann.

An dieser Stelle kommen die Dekoratoren ins Spiel. Wir können eine Funktion namens debugMethod definieren, die wie folgt aussieht:

function debugMethod(originalMethod: any, context: any) {
    function replacementMethod(this: any, ...args: any[]) {
        console.log('Method Execution Starts.');
        const result = originalMethod.call(this, ...args);
        console.log('Method Execution Ends.');
        return result;
    }
    return replacementMethod;
}

Im obigen Code nimmt debugMethod die ursprüngliche Methode (originalMethod) und gibt eine Funktion zurück, die Folgendes tut:

  1. Sie protokolliert die Meldung „Die Ausführung der Methode beginnt“.
  2. Übergibt die ursprüngliche Methode und alle ihre Argumente (einschließlich dieser).
  3. Protokolliert die Meldung „Die Ausführung der Methode endet“.
  4. Gibt zurück, was die ursprüngliche Methode zurückgegeben hat.

Durch die Verwendung von Dekoratoren kannst du die debugMethod auf deine Methoden anwenden, wie im folgenden Code gezeigt:

class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    @debugMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
    @debugMethod
    getAge() {
        console.log(`I am ${this.age} years old.`);
    }
}
const p = new Person('Ron', 30);
p.greet();
p.getAge();

Dies gibt das Folgende aus:

LOG: Entering method.
Hello, my name is Ron.
LOG: Exiting method.
LOG: Entering method.
I am 30 years old.
LOG: Exiting method.

Bei der Definition der Decorator-Funktion (debugMethod) wird ein zweiter Parameter mit dem Namen context übergeben (es ist das Kontextobjekt – es enthält einige nützliche Informationen darüber, wie die dekorierte Methode deklariert wurde und auch den Namen der Methode). Du kannst dein debugMethod aktualisieren, um den Methodennamen aus dem context Objekt zu erhalten:

function debugMethod(
    originalMethod: any,
    context: ClassMethodDecoratorContext
) {
    const methodName = String(context.name);
    function replacementMethod(this: any, ...args: any[]) {
        console.log(`'${methodName}' Execution Starts.`);
        const result = originalMethod.call(this, ...args);
        console.log(`'${methodName}' Execution Ends.`);
        return result;
    }
    return replacementMethod;
}

Wenn du deinen Code ausführst, enthält die Ausgabe jetzt den Namen jeder Methode, die mit dem debugMethod Dekorator dekoriert wurde:

'greet' Execution Starts.
Hello, my name is Ron.
'greet' Execution Ends.
'getAge' Execution Starts.
I am 30 years old.
'getAge' Execution Ends.

Es gibt noch mehr, was du mit Dekoratoren machen kannst. Weitere Informationen über die Verwendung von Dekoratoren in TypeScript findest du im ursprünglichen Pull Request.

Einführung von const Type Parameters

Dies ist eine weitere große Neuerung, die dir mit Generics ein neues Werkzeug an die Hand gibt, mit dem du die Inferenz beim Aufruf von Funktionen verbessern kannst. Wenn du Werte mit const deklarierst, schlussfolgert TypeScript standardmäßig den Typ und nicht die literalen Werte:

// Inferred type: string[]
const names = ['John', 'Jake', 'Jack'];

Um die gewünschte Schlussfolgerung zu erhalten, musstest du bisher die Const-Assertion verwenden, indem du „as const“ hinzufügst:

// Inferred type: readonly ["John", "Jake", "Jack"]
const names = ['John', 'Jake', 'Jack'] as const;

Wenn du Funktionen aufrufst, ist es ähnlich. Im folgenden Code ist der gefolgerte Typ von Ländern string[]:

type HasCountries = { countries: readonly string[] };
function getCountriesExactly(arg: T): T['countries'] {
    return arg.countries;
}

// Inferred type: string[]
const countries = getCountriesExactly({ countries: ['USA', 'Canada', 'India'] });

Möglicherweise möchtest du einen spezifischeren Typ, den du bisher nur durch Hinzufügen der as const Assertion lösen konntest:

// Inferred type: readonly ["USA", "Canada", "India"]
const names = getNamesExactly({ countries: ['USA', 'Canada', 'India'] } as const);

Das kann schwierig zu merken und zu implementieren sein. TypeScript 5.0 führt jedoch eine neue Funktion ein, mit der du einen const-Modifikator zu einer Typparameter-Deklaration hinzufügen kannst, der automatisch eine const-ähnliche Inferenz als Standard anwendet.

type HasCountries = { countries: readonly string[] };
function getNamesExactly(arg: T): T['countries'] {
    return arg.countries;
}

// Inferred type: readonly ["USA", "Canada", "India"]
const names = getNamesExactly({ countries: ['USA', 'Canada', 'India'] });

Die Verwendung von const Typparametern ermöglicht es Entwicklern, ihre Absicht in ihrem Code deutlicher auszudrücken. Wenn eine Variable konstant sein und sich nie ändern soll, stellt die Verwendung eines const Typparameters sicher, dass sie nicht versehentlich geändert werden kann.

Weitere Informationen darüber, wie der Typparameter const in TypeScript funktioniert, findest du im ursprünglichen Pull Request.

Verbesserungen an Enums

Enums in TypeScript sind ein leistungsfähiges Konstrukt, das es Entwicklern ermöglicht, eine Reihe von benannten Konstanten zu definieren. In TypeScript 5.0 wurden Verbesserungen an Enums vorgenommen, um sie noch flexibler und nützlicher zu machen.

Wenn du zum Beispiel die folgende Enum in einer Funktion übergeben hast:

enum Color {
    Red,
    Green,
    Blue,
}

function getColorName(colorLevel: Color) {
    return colorLevel;
}

console.log(getColorName(1));

Vor der Einführung von TypeScript 5.0 konntest du eine falsche Levelnummer übergeben und es wurde kein Fehler ausgegeben. Aber mit der Einführung von TypeScript 5.0 wird sofort ein Fehler ausgegeben.

Außerdem werden in der neuen Version alle Enums zu Union Enums, indem für jedes berechnete Element ein eigener Typ erstellt wird. Diese Verbesserung ermöglicht es, alle Enums einzugrenzen und ihre Mitglieder als Typen zu referenzieren:

enum Color {
    Red,
    Purple,
    Orange,
    Green,
    Blue,
    Black,
    White,
}

type PrimaryColor = Color.Red | Color.Green | Color.Blue;

function isPrimaryColor(c: Color): c is PrimaryColor {
    return c === Color.Red || c === Color.Green || c === Color.Blue;
}

console.log(isPrimaryColor(Color.White)); // Outputs: false
console.log(isPrimaryColor(Color.Red)); // Outputs: true

Leistungsverbesserungen von TypeScript 5.0

TypeScript 5.0 enthält zahlreiche wichtige Änderungen an der Codestruktur, den Datenstrukturen und den algorithmischen Erweiterungen. Dies hat dazu beigetragen, das gesamte TypeScript-Erlebnis von der Installation bis zur Ausführung zu verbessern und es schneller und effizienter zu machen.

Der Unterschied zwischen der Paketgröße von TypeScript 5.0 und 4.9 ist zum Beispiel ziemlich beeindruckend.

TypeScript wurde kürzlich von namespaces zu modules migriert, wodurch es moderne Build-Tools nutzen kann, die Optimierungen wie Scope Hoisting durchführen können. Außerdem konnten durch das Entfernen von veraltetem Code 26,4 MB von den 63,8 MB der TypeScript 4.9-Paketgröße eingespart werden.

TypeScript-Paketgröße
TypeScript-Paketgröße

Hier sind noch ein paar weitere interessante Verbesserungen bezüglich Geschwindigkeit und Größe zwischen TypeScript 5.0 und 4.9:

Szenario Zeit oder Größe im Vergleich zu TS 4.9
material-ui Erstellungszeit 90%
TypeScript Compiler-Startzeit 89%
Playwright Erstellungszeit 88%
TypeScript Compiler-Selbstbauzeit 87%
Outlook Web Erstellungszeit 82%
VS Code Erstellungszeit 80%
typescript npm Paketgröße 59%

Bundler-Auflösung für bessere Modulauflösung

Wenn du eine Import-Anweisung in TypeScript schreibst, muss der Compiler wissen, worauf sich der Import bezieht. Das macht er mit der Modulauflösung. Wenn du zum Beispiel import { a } from "moduleA" schreibst, muss der Compiler die Definition von a in moduleA kennen, um seine Verwendung zu überprüfen.

In TypeScript 4.7 wurden zwei neue Optionen für die Einstellungen --module und moduleResolution hinzugefügt: node16 und nodenext.

Der Zweck dieser Optionen war es, die genauen Lookup-Regeln für ECMAScript-Module in Node.js genauer darzustellen. Dieser Modus hat jedoch einige Einschränkungen, die von anderen Tools nicht beachtet werden.

Zum Beispiel muss in einem ECMAScript-Modul in Node.js jeder relative Import eine Dateierweiterung enthalten, damit er korrekt funktioniert:

import * as utils from "./utils"; // Wrong 

import * as utils from "./utils.mjs"; // Correct

TypeScript hat eine neue Strategie namens „moduleResolution bundler“ eingeführt Diese Strategie kannst du umsetzen, indem du den folgenden Code in den Abschnitt „compilerOptions“ deiner TypeScript-Konfigurationsdatei einfügst:

{
    "compilerOptions": {
        "target": "esnext",
        "moduleResolution": "bundler"
    }
}

Diese neue Strategie eignet sich für alle, die moderne Bundler wie Vite, esbuild, swc, Webpack, Parcel und andere verwenden, die eine hybride Lookup-Strategie nutzen.

Weitere Informationen darüber, wie moduleResolution bundler in TypeScript funktioniert, findest du im ursprünglichen Pull Request und seiner Implementierung.

Abwertungen

TypeScript 5.0 bringt einige Abwertungen mit sich, darunter Laufzeitanforderungen, Änderungen an lib.d.ts und API-Änderungen.

  1. Laufzeitanforderungen: TypeScript zielt jetzt auf ECMAScript 2018 ab, und das Paket setzt eine minimale Engine-Erwartung von 12.20. Daher sollten Nutzer von Node.js mindestens die Version 12.20 oder höher haben, um TypeScript 5.0 zu verwenden.
  2. lib.d.ts Änderungen: Es wurden einige Änderungen an der Art und Weise vorgenommen, wie Typen für das DOM generiert werden, was sich auf bestehenden Code auswirken kann. Insbesondere wurden bestimmte Eigenschaften von Zahlentypen in numerische Literaltypen umgewandelt, und Eigenschaften und Methoden für die Ereignisbehandlung beim Ausschneiden, Kopieren und Einfügen wurden in andere Schnittstellen verschoben.
  3. API-Änderungen: Einige unnötige Schnittstellen wurden entfernt und die Korrektheit wurde verbessert. TypeScript 5.0 ist außerdem auf modules umgestiegen.

Mit TypeScript 5.0 wurden bestimmte Einstellungen und die dazugehörigen Werte veraltet, darunter target: ES3, out, noImplicitUseStrict, keyofStringsOnly, suppressExcessPropertyErrors, suppressImplicitAnyIndexErrors, noStrictGenericChecks, charset, importsNotUsedAsValues und preserveValueImports, sowie das Voranstellen in Projektreferenzen.

Diese Konfigurationen bleiben zwar bis TypeScript 5.5 gültig, aber es wird eine Warnung ausgegeben, um Benutzer zu erreichen, die sie noch verwenden.

Zusammenfassung

In diesem Artikel hast du einige der wichtigsten Funktionen und Verbesserungen kennengelernt, die TypeScript 5.0 mit sich bringt, z. B. Verbesserungen bei Enums, Bundler-Auflösung und Const-Type-Parametern sowie Verbesserungen bei Geschwindigkeit und Größe.

Wenn du darüber nachdenkst, TypeScript für deine nächsten Projekte einzusetzen, solltest du das Anwendungs-Hosting von Kinsta kostenlos ausprobieren.

Jetzt bist du dran! Welche Funktionen oder Verbesserungen findest du in TypeScript 5.0 am interessantesten? Gibt es wichtige Neuerungen, die wir vielleicht übersehen haben? Lass es uns in den Kommentaren wissen.

Joel Olawanle Kinsta

Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 200 technical articles majorly around JavaScript and it's frameworks.