Grundlagen von TypeScript: Von der ersten Anmerkung bis zu Generics und Strict Mode
Eine strukturierte Einführung in das Typsystem von TypeScript – von Primitiven und Inferenz bis hin zu diskriminierten Unionen, Generika, Hilfstypen sowie einer strengen tsconfig.
Jeder JavaScript-Entwickler kennt dieses Muster: Der Code funktioniert lokal, wird bereitgestellt – und zwei Tage später kommt ein Fehlerbericht, weil eine Funktion statt einer Zeichenkette ein Objekt erhalten hat oder undefined in eine Berechnung eingeflossen ist. JavaScript hindert Sie nicht daran, solchen Code zu schreiben; er versagt lediglich später, beim Ausführen, meist in der Produktion. TypeScript verlagert dieses Versagen auf den Moment, in dem Sie die Zeile eingeben. Dieser Leitfaden führt Sie von einem ersten let x: number über Einschränkungen, diskriminierte Unionen, Generics und Hilfstypen bis zu den Compiler-Einstellungen, die bestimmen, wie viel Schutz Sie tatsächlich erhalten – damit Sie TypeScript in der Produktion selbstbewusst lesen und schreiben können.
Was TypeScript ist – und was es nicht ist
TypeScript ist eine Open-Source-Sprache von Microsoft, die ein statisches Typsystem zu JavaScript hinzufügt. Unter „statisch“ versteht man hier, dass der Compiler die Typen vor der Ausführung – im Editor oder zur Kompilierzeit – überprüft, anstatt sie erst während der Ausführung zu erkennen. Drei Fakten bestimmen alles Weitere:
- Es ist ein Superset von JavaScript. Jede syntaktisch gültige JavaScript-Codezeile ist auch eine gültige TypeScript-Codezeile, sodass man auf dem Wissen aufbaut, das bereits vorhanden ist, anstatt von vorne anzufangen. Der Prüfer kann bei solchem Code dennoch Fehler melden – genau das ist der Zweck.
- Es wird in reines JavaScript kompiliert. Browser und Node.js führen JavaScript aus, weshalb der Compiler
tscdie Anmerkungen entfernt und normale.js-Dateien erzeugt.
Der Unterschied in einer Funktion
Hier ist eine gewöhnliche JavaScript-Funktion, die zwei Werte addiert:
function add(a, b) {
return a + b;
}
Wenn sie mit einem String und einer Zahl aufgerufen wird, wirft sie keinen Fehler aus. Sie verknüpft die Werte miteinander, und man bemerkt das Problem erst, wenn eine Summe auf einer Rechnung ungewöhnlich erscheint:
add("10", 20); // "1020" — silently wrong
Durch Angabe der Parameter- und Rückgabetypen wird der Vertrag explizit festgelegt:
function add(a: number, b: number): number {
return a + b;
}
Nun wird derselbe Aufruf bereits während der Bearbeitung abgelehnt, wobei der Fehler direkt unter dem fehlerhaften Argument angezeigt wird:
add("10", 20);
// Error: Argument of type 'string' is not assignable to parameter of type 'number'.
Ein Projekt einrichten
Sobald Node.js installiert ist, können Sie den Compiler global installieren und überprüfen, ob er funktioniert:
npm install -g typescript
tsc --version
Für echte Projekte sollte TypeScript als lokale Entwicklungsabhängigkeit installiert werden, damit alle Mitwirkenden sowie der CI-Server dieselbe Version verwenden. Anschließend wird eine Konfigurationsdatei erstellt:
npm install typescript --save-dev
npx tsc --init
Die erzeugte tsconfig.json-Datei dient als Steuerungselement dafür, wie streng und modern der Compiler sein soll. Neuere Versionen von tsc --init aktivieren bereits den strict-Modus, und TypeScript 6.0 machte den strengen Modus standardmäßig. Ältere Projekte laufen jedoch oft mit nachsichtigeren Einstellungen, die Teams später verschärfen. Der folgende Konfigurationsabschnitt befasst sich damit.
In einer minimalen ersten Datei wird eine typisierte Variable deklariert und ausgegeben:
// app.ts
let message: string = "Hello, TypeScript";
console.log(message);
Kompilieren Sie sie anschließend und führen Sie das erzeugte JavaScript aus:
tsc app.ts # produces app.js
node app.js # Hello, TypeScript
Täglich vermeiden die meisten Projekte diesen zweistufigen Ablauf und führen Dateien mit ts-node oder tsx aus, oder sie nutzen einen Bundler wie Vite, esbuild oder webpack, der in Echtzeit kompiliert. Es lohnt sich dennoch, es einmal manuell durchzuführen, denn dadurch wird das richtige Denkmuster verankert: TypeScript geht hinein, JavaScript kommt heraus.
Basistypen und Inferenz
Primitiven und Arrays
Die Primitiv-Annotierungen sind string, number und boolean:
let username: string = "sanajit";
let age: number = 26;
let isActive: boolean = true;
Arrays werden mit dem Elementtyp gefolgt von Klammern geschrieben:
let scores: number[] = [90, 85, 78];
let tags: string[] = ["typescript", "javascript"];
Die generische Form Array<number> bedeutet genau dasselbe; wählen Sie einen Stil und verwenden Sie ihn konsequent:
// equivalent generic syntax
let ids: Array<number> = [1, 2, 3];
Lassen Sie die Inferenz die offensichtliche Arbeit übernehmen
Man muss initialisierte Variablen nur selten annotieren. TypeScript schließt den Typ aus dem Wert ab und stellt ihn anschließend durch.
let city = "Ahmedabad"; // inferred as string
city = 42; // Error: Type 'number' is not assignable to type 'string'
Die Schreibweise let city: string = „Ahmedabad“ ist nicht falsch, aber überflüssig. Eine gute Regel für die gesamte Sprache lautet: Verlassen Sie sich auf die Automatisierung, wenn der Wert den Typ bereits klar macht, und geben Sie explizite Typen an, wenn ein Wert unklar ist oder Sie einen Vertrag definieren, wie beispielsweise Funktionsparameter, Rückgabetypen, leere Arrays und Callback-Signaturen.
any, unknown, never und void
Diese vier speziellen Typen bereiten fast jedem Anfänger Probleme:
anydeaktiviert die Typüberprüfung für einen Wert. Es handelt sich dabei um eine Notlösung statt um einen echten Typ, und sein übermäßiger Gebrauch ist die häufigste Ursache dafür, dass Codebasen letztendlich mit zusätzlicher Syntax im Stil von JavaScript enden.
unknown ist die sichere Alternative. Jeder Wert kann ihm zugewiesen werden, doch man kann ihn erst verwenden, nachdem er durch eine Überprüfung eingegrenzt wurde. Er eignet sich besonders für Daten, deren Struktur noch unbekannt ist, wie beispielsweise eine API-Antwort vor der Validierung.never beschreibt einen Wert, der nicht existieren kann – zum Beispiel das Ergebnis einer Funktion, die stets Fehler auslöst oder niemals einen Wert zurückgibt. Sein praktischstes Anwendungsgebiet ist die Prüfung auf Vollständigkeit, wodurch der Compiler sicherstellen kann, dass jeder Fall in einem switch abgehandelt wird.void kennzeichnet eine Funktion, die keinen nützlichen Wert zurückgibt, wie beispielsweise ein Ereignishandler oder eine Logging-Verpackungsfunktion.Mit unknown ermöglicht eine typeof-Überprüfung die Verwendung der String-Methoden nur innerhalb des geschützten Zweigs:
function process(value: unknown) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // safe — narrowed to string
}
}
Eine Funktion, die immer einen Fehler auslöst, wird als never typisiert, während eine Funktion, die lediglich einen Nebeneffekt ausführt, void zurückgibt:
function fail(message: string): never {
throw new Error(message);
}function logAction(action: string): void {
console.log(`Action: ${action}`);
}
Für konkrete Ersatzlösungen für any in gängigen Situationen siehe sechs typsichere Muster zur Ersetzung von any.
Objekte mit Schnittstellen und Typaliasen beschreiben
Die Struktur eines Objekts kann direkt im Code geschrieben werden:
const employee: { id: number; name: string; active: boolean } = {
id: 1,
name: "Amit",
active: true,
};
Das funktioniert einmal, doch die Wiederholung derselben Struktur überall wird schnell zu Überfluss. Die Benennung der Struktur mit einer interface oder einem type löst dieses Problem.
Schnittstellen
Eine Schnittstelle gibt die Struktur eines Objekts an, damit sie wiederverwendet werden kann:
interface Employee {
id: number;
name: string;
department: string;
}
Jedes mit ihr annotierte Objekt muss den deklarierten Feldern entsprechen:
const employee1: Employee = { id: 1, name: "John", department: "IT" };
const employee2: Employee = { id: 2, name: "Sara", department: "HR" };
Ein Fragezeichen kennzeichnet eine Eigenschaft, die fehlen kann:
interface User {
id: number;
name: string;
phone?: string; // may or may not be present
}
readonly-Eigenschaften können bei der Erstellung des Objekts zugewiesen werden, aber nicht danach:
interface Product {
readonly id: number;
name: string;
}
Ein erneutes Zuweisen einer solchen Eigenschaft führt zu einem Kompilierfehler:
const laptop: Product = { id: 100, name: "MacBook" };
laptop.id = 200; // Error: Cannot assign to 'id' because it is a read-only property
Interfacen können sich ebenfalls gegenseitig erweitern, was es ermöglicht, gemeinsame Felder ohne Kopieren zu teilen. Beginnen Sie mit einer Basisklasse:
interface Person {
name: string;
age: number;
}
Erstellen Sie anschließend eine spezifischere Klasse darauf auf:
interface Employee extends Person {
employeeId: number;
department: string;
}
Typ-Aliasse
Ein type-Alias kann auch Objektstrukturen benennen, ist aber nicht darauf beschränkt. Unionen, Primitive und Tupel können alle auf diese Weise einen Namen erhalten:
type ID = string | number;
type Point = { x: number; y: number };
type Status = "pending" | "shipped" | "delivered";
Wahl zwischen Interface und Typ
Für alltägliche Objektstrukturen sind die beiden im Großen und Ganzen austauschbar. Die wirklichen Unterschiede liegen bei:
- Erweiterung: Interfaces verwenden
extends; Typ-Alias kombinieren Strukturen mithilfe des&-Intersektionsoperators. - Unionen und Primitive: Nur Typ-Alias können sie darstellen, wie in
type A = string | number. - Deklarationsverschmelzung: Zwei Interfaces mit demselben Namen verschmelzen zu einem; Typ-Alias können nicht erneut deklariert werden.
- Konvention: Interfaces sind typisch für öffentliche API-Strukturen und Klassenverträge; Typ-Alias werden für Unionen, Tupel sowie mappierte oder bedingte Typen verwendet.
Eine Regel, die viele Teams befolgen: Verwenden Sie interface, wenn eine Struktur voraussichtlich erweitert werden soll – beispielsweise React-Props, API-Modelle und Klassenverträge – und verwenden Sie type für Unionen, Tupel sowie alle nicht-Objekt-Typen.
Unionen, Einschränkung und Intersektionen
Union-Typen
Eine Union besagt, dass ein Wert einer von mehreren Typen sein kann:
function printId(id: string | number) {
console.log(`Your ID is ${id}`);
}
Sowohl die untenstehenden Aufrufe sind zulässig, da jedes Argument mit einem Mitglied der Union übereinstimmt:
printId(101);
printId("A-204");
Einschränkung
Wenn ein Wert als Union typisiert ist, erlaubt TypeScript nur Operationen, die für jedes Mitglied gültig sind, bis nachgewiesen wird, um welches Mitglied es sich handelt. Dieser Nachweis wird als Einschränkung bezeichnet, und der Compiler folgt ihr durch den gesamten Kontrollfluss:
function formatValue(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase(); // TypeScript knows it's a string here
}
return value.toFixed(2); // and here, it knows it's a number
}
Neben typeof sind die gängigen Mittel zur Einschränkung Array.isArray(), instanceof, der in-Operator sowie Gleichheitsprüfungen gegen null oder undefined. So bleibt TypeScript im Normalfall ruhig und weist dennoch ungewöhnliche Fälle aus.
Diskriminierte Unions für Zustände
Muster, die Entwickler auf mittlerem Niveau am häufigsten übersehen, sind das Vergeben eines gemeinsamen literalen „Tag“-Feldes für jede Variante einer Union. Zuerst muss jeder Zustand separat definiert werden:
type LoadingState = { status: "loading" };
type SuccessState = { status: "success"; data: string[] };
type ErrorState = { status: "error"; message: string };
Dann werden sie kombiniert und anhand des Tags ausgewählt:
type FetchState = LoadingState | SuccessState | ErrorState;function render(state: FetchState) {
switch (state.status) {
case "loading":
return "Loading...";
case "success":
return `Loaded ${state.data.length} items`;
case "error":
return `Failed: ${state.message}`;
}
}
In jedem case wird der Wert von state auf die entsprechende Variante eingegrenzt, sodass state.data nur im "success"-Zweig und state.message nur im "error"-Zweig verfügbar ist. Unmögliche Kombinationen, wie zum Beispiel gleichzeitig Daten und ein Fehler, können einfach nicht dargestellt werden. Dadurch entfällt eine ganze Kategorie von Abstürzen vom Typ „Eigenschaft ist undefiniert“ in UI- und API-Code. Durch Hinzufügen eines default-Zweigs, der state einer never-Variable zuweist, wird dies zu einer Überprüfung auf Vollständigkeit: Fügt man später einen vierten Zustand hinzu, weist der Compiler auf alle Switch-Anweisungen hin, die diesen übersehen haben.
Intersektionstypen
Während eine Union „dies oder jenes“ bedeutet, steht eine Intersektion für „dies und jenes“. Beginnen wir mit zwei kleinen Strukturen:
type Timestamped = { createdAt: Date };
type Named = { name: string };
Ein Schnittpunkt erfordert, dass ein Objekt die Felder beider Typen enthält:
type Record = Timestamped & Named;const item: Record = { name: "Invoice", createdAt: new Date() };
Eine Warnung zu diesem Beispiel: Record ist auch der Name eines eingebauten Hilftyps. Wenn Sie einen eigenen Alias mit diesem Namen deklarieren, überschattet dieser den globalen Typ in dieser Datei – was im besten Fall verwirrend ist. Verwenden Sie daher in echtem Code einen spezifischeren Namen.
Funktionen
Parameter- und Rückgabeanmerkungen bilden den Kern des Vertrags einer Funktion:
function multiply(a: number, b: number): number {
return a * b;
}
Optionale Parameter werden mit ? gekennzeichnet, Standardwerte machen einen Parameter optional und bestimmen dessen Typ automatisch, während Restparameter beliebig viele Argumente in ein typisiertes Array sammeln:
// optional parameter
function greet(name: string, title?: string): string {
return title ? `${title} ${name}` : name;
}// default parameter
function createOrder(item: string, quantity: number = 1) {
return { item, quantity };
}// rest parameters
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0);
}
Funktionsarten
Auch die Struktur der Funktion selbst kann beschrieben werden, was bei Callbacks und Strategieobjekten nützlich ist:
type MathOperation = (a: number, b: number) => number;
Eine derartigen Typ zugeordnete Funktion bezieht ihre Parametertypen aus der Annotation, weshalb a und b keine eigenen Annotationen benötigen:
const subtract: MathOperation = (a, b) => a - b;
Klassen und Zugriffsmodifikatoren
TypeScript-Klassen sind JavaScript-Klassen mit typisierten Eigenschaften und Zugriffsmodifikatoren. Die folgende Klasse beginnt mit der Deklaration eines privaten Saldo-Werts sowie eines nur zum Lesen zugänglichen Eigentümers:
class Account {
private balance: number;
readonly owner: string;
Der Rest der Klasse setzt diese Felder im Konstruktor fest und stellt Methoden zur Änderung sowie zum Lesen des Saldo-Werts bereit; der Zugriff auf das private Feld von außen wird abgelehnt:
constructor(owner: string, initialBalance: number) {
this.owner = owner;
this.balance = initialBalance;
} deposit(amount: number): void {
this.balance += amount;
} getBalance(): number {
return this.balance;
}
}const acc = new Account("Priya", 1000);
acc.deposit(500);
console.log(acc.getBalance()); // 1500
acc.balance; // Error: Property 'balance' is private
Die Modifikatoren bedeuten:
public, der Standardwert, ist überall zugänglich.privateist nur innerhalb der Klasse zugänglich.protectedist innerhalb der Klasse sowie ihrer Unterklassen zugänglich.
readonly-Felder erhalten einen Wert im Konstruktor und lehnen spätere Neuzuweisungen ab.Beachten Sie, dass private nur vom Compiler durchgesetzt wird; zur Laufzeit ist die Eigenschaft ein gewöhnliches Feld. Wenn Sie echte Privatsphäre zur Laufzeit benötigen, bieten JavaScripts #-Felder diese Möglichkeit – ein Kompromiss, der in TypeScript private Fields versus # Syntax erläutert wird.
Interfaces als Klassenverträge
Ein Interface kann definieren, was eine Klasse bereitstellen muss:
interface Shape {
area(): number;
}
Mithilfe von implements überprüft der Compiler, ob die Klasse den Vertrag erfüllt, und meldet fehlende Methoden bereits vor dem Ausführen des Codes. Der Konstruktor verwendet außerdem eine Parameter-Eigenschaft, private radius, mit der das Feld in einem Schritt deklariert und zugewiesen wird:
class Circle implements Shape {
constructor(private radius: number) {} area(): number {
return Math.PI * this.radius ** 2;
}
}
Generics: wiederverwendbarer Code, der seine Typen beibehält
Generics sind in der Regel der Punkt, an dem TypeScript nicht mehr wie annotierter JavaScript wirkt, sondern eher wie ein anderes Werkzeug.
Das Problem, das sie lösen
Ein Hilfsfunktion, die das erste Element eines beliebigen Arrays zurückgibt, kann mit any geschrieben werden:
function firstElement(arr: any[]) {
return arr[0];
}
Es funktioniert, doch die Typinformationen gehen verloren, und jedes Ergebnis ist any:
const num = firstElement([1, 2, 3]); // typed as `any` — no help from the compiler
const str = firstElement(["a", "b"]); // also `any`
Ein Typparameter erfasst den Elementtyp der Eingabe und verwendet ihn für den Rückgabewert:
function firstElement<T>(arr: T[]): T {
return arr[0];
}
Nun erhält jeder Aufruf einen präzisen Ergebnistyp, der aus dem Argument abgeleitet wird:
const num = firstElement([1, 2, 3]); // inferred as number
const str = firstElement(["a", "b"]); // inferred as string
T ist eine Typvariable, die TypeScript anhand dessen, was Sie übergeben, ausfüllt. Sie behalten die Flexibilität von any bei und erhalten gleichzeitig die Sicherheit eines konkreten Typs. Beachten Sie, dass bei einem leeren Array arr[0] zur Laufzeit tatsächlich undefined ist; die Aktivierung von noUncheckedIndexedAccess lässt den Compiler dies als T | undefined widerspiegeln.
Generische Schnittstellen
Generics funktionieren auch bei Schnittstellen. Ein einziger Response-Wrapper kann jeden Endpunkt beschreiben:
interface ApiResponse<T> {
success: boolean;
data: T;
}
Der Typ des Payloads wird dort angegeben, wo der Wrapper verwendet wird:
const userResponse: ApiResponse<{ id: number; name: string }> = {
success: true,
data: { id: 1, name: "Sara" },
};
So typisieren viele Anwendungen ihre API-Schicht: ein einziger ApiResponse<T>, der für Benutzer, Produkte, Bestellungen sowie alles andere, was der Backend zurückgibt, wiederverwendet wird.
Einschränkung eines Typparameters
Mannchmal muss T bestimmte Eigenschaften garantieren. Beschreiben Sie die Anforderung als eine Schnittstelle:
interface HasLength {
length: number;
}
Beschränken Sie den Parameter anschließend mit extends, sodass nur Typen mit einer length-Eigenschaft akzeptiert werden:
function logLength<T extends HasLength>(item: T): void {
console.log(item.length);
}logLength("hello"); // OK — strings have .length
logLength([1, 2, 3]); // OK — arrays have .length
logLength(42); // Error: number doesn't have .length
Hilftypen
TypeScript bietet generische Hilftypen, die aus vorhandenen Typen neue ableiten und damit viel manuell geschriebene Standardcode ersetzen. Angenommen, es gibt ein Benutzermodell:
interface User {
id: number;
name: string;
email: string;
isAdmin: boolean;
}
Man kann Varianten ableiten anstelle sie erneut zu deklarieren: Partial macht jede Eigenschaft optional, Readonly macht sie alle nur für Lesezugriff zugänglich, Pick behält ausgewählte Schlüssel bei, Omit entfernt sie und Record erstellt einen Dictionary-Typ:
// Every property becomes optional — perfect for "update" functions
type UserUpdate = Partial<User>;// Every property becomes read-only
type ImmutableUser = Readonly<User>;// Pick only the fields you need
type UserPreview = Pick<User, "id" | "name">;// Everything except the fields you list
type PublicUser = Omit<User, "email" | "isAdmin">;// A dictionary shape: keys of one type, values of another
type UsersById = Record<number, User>;
Eine typische Anwendung ist eine Aktualisierungsfunktion, die jedes beliebige Teilmenge der Felder akzeptiert:
function updateUser(id: number, changes: Partial<User>): void {
// merge `changes` into the stored user
}
Die Aufrufer übergeben nur das, was sich geändert hat:
updateUser(1, { name: "New Name" }); // no need to pass email, isAdmin, etc.
Der Blog-Guide zu TypeScripts eingebauten Utility-Typen geht tiefer auf die vollständige Sammlung ein.
Enums und Literal-Typen
Enums
Eine Enum definiert eine benannte Menge von Konstanten:
enum OrderStatus {
Pending,
Shipped,
Delivered,
}
Die Werte werden anschließend über die Enum referenziert:
let status: OrderStatus = OrderStatus.Shipped;
Standardmäßig werden die Mitglieder von 0 an nummeriert. Durch das Zuweisen von String-Werten werden Logs und Netzwerkdaten deutlich leichter zu debuggen:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
Unionen von String-Literalen sind oft einfacher
Viele Teams bevorzugen heutzutage Unionen von String-Literalen, da dadurch kein zusätzlicher JavaScript-Code erzeugt wird und das Verhalten vorhersehbarer ist:
type OrderStatus = "pending" | "shipped" | "delivered";
Der Compiler lehnt weiterhin alle Werte außerhalb der definierten Menge ab:
function updateStatus(status: OrderStatus) {
// ...
}updateStatus("shipped"); // OK
updateStatus("cancelled"); // Error: not assignable to type 'OrderStatus'
Es gibt einen weiteren praktischen Grund, Union-Typen zu bevorzugen: Enums erzeugen Laufzeitcode, weshalb Tools, die nur Typen entfernen, wie beispielsweise die eingebaute TypeScript-Unterstützung von Node.js, sie nicht akzeptieren.
Module und tsconfig.json
Module
TypeScript verwendet die standardmäßige ES-Module-Syntax. Eine Datei exportiert eine Funktion:
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
Eine andere Datei importiert sie:
// app.ts
import { add } from "./math";
Die wichtigsten Einstellungen
Eine generierte Konfiguration listet Dutzende von Optionen auf, doch nur wenige bestimmen den Großteil der Benutzererfahrung:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist"
}
}
strict: trueaktiviert die gesamte Reihe an strengen Überprüfungen, einschließlichstrictNullChecks, das eine explizite Handhabung vonnullundundefinederzwingt. Erfahrene Entwickler betrachten dies als unverhandelbar, denn ohne diese Einstellung erkennt TypeScript weitaus weniger echte Fehler.
noImplicitAny meldet einen Fehler dort, wo ein Wert sonst ohne Ihre Anweisung auf any zurückgegriffen würde. Er ist bereits Teil von strict, weshalb seine gesonderte Aufzählung nur der Lesbarkeit dient.target bestimmt, welche JavaScript-Version ausgeliefert wird und somit, inwieweit moderne Syntax für ältere Laufzeiten umgeschrieben werden muss.Wenn Sie eine veraltete Codebasis erben, bei der strict deaktiviert ist, ist der nachhaltige Ansatz, es zu aktivieren und die Fehler nacheinander in den einzelnen Dateien zu beheben, anstatt any einzusetzen, bis die roten Fehlermeldungen verschwinden.
Häufige Fehler auf jeder Ebene
Anfänger
- Das Annotieren von Werten, die TypeScript ohnehin ermitteln würde.
- Sofort auf
anyzurückzugreifen, sobald der Compiler einen Fehler meldet, anstatt den tatsächlichen Typ einzugrenzen.
Mittelstufe
- Eine
InterfaceoderTypefür eine einmalige Form deklarieren, die eigentlich von der Inferenz erfasst werden sollte – was unnötige Formalitäten mit sich bringt. - Arrays als veränderlich definieren und sie mit
pushoderspliceändern, obwohl sie eigentlichReadonlyArray<T>sein sollten und unveränderlich aktualisiert werden müssen. - Vergessen, dass eine Union zunächst eingegrenzt werden muss, bevor typspezifische Member verfügbar sind – und stattdessen gegen den Compiler vorgehen anstatt seine Meldungen zu lesen.
Fortgeschritten
- Unbeschränkte Generics schreiben, die alles sein könnten – was heimlich den Zweck der Verwendung von Generics untergräbt.
strictfür das gesamte Projekt deaktivieren, um ein paar Fehler zu verbergen, anstatt sie zu beheben oder in einen bestimmten Bereich einzuschränken.
value as Type, als würde sie etwas überprüfen. Eine Behauptung prüft zur Laufzeit nichts; sie teilt dem Compiler lediglich mit, Ihnen vertrauen zu dürfen. Daten von außerhalb Ihres Programms, einschließlich API-Antworten, Benutzereingaben und localStorage, benötigen eine echte Überprüfung.Hauptpunkte
- TypeScript fügt JavaScript einen Überprüfer zur Kompilierzeit hinzu; die Typen verschwinden, sobald der Code ausgeführt wird, weshalb Laufzeitgrenzen weiterhin überprüft werden müssen.
- Annotieren Sie Verträge wie Funktionssignaturen und leere Sammlungen und überlassen Sie die restliche Verarbeitung der Inferenz.
- Verwenden Sie
interfacefür erweiterbare Objektstrukturen undtypefür Unionen, Tupel sowie nicht-objektbasierte Typen. - Lernen Sie frühzeitig diskriminierte Unionen kennen; sie sind das Muster, das am direktesten Fehler in state-reichen Code verhindert.
- Generische Typen und Hilfstypen wie
Partial,Pick,OmitundRecordsind es, die verstreute Anmerkungen in ein echtes Typsystem verwandeln. - Lassen Sie
strict: trueaktiv. Der Wert von TypeScript besteht darin, Fehler frühzeitig aufzudecken, anstatt dass man sie später zu höheren Kosten entdeckt – und das funktioniert nur, wenn der Prüfer seine Arbeit vollständig ausführen darf.
Zusätzliche Literatur
- Sechs TypeScript-Techniken, die Typen in echte Fehlerverhütungsmittel verwandeln – Erfahren Sie, wie satisfies, markierte Unionen, never checks, unknown, abgeleitete Typen und branded IDs dazu beitragen, dass TypeScript echte Fehler bereits zur Kompilierzeit statt in der Produktion aufspürt.
- Modellierung von Domänen in TypeScript: Jenseits der grundlegenden Typangaben — Erlernen Sie praktische TypeScript-Methoden – von unknown vs any über diskriminierte Unionen bis zu satisfies –, die Ihnen helfen, gültige Zustände zu modellieren anstatt lediglich Daten zu kennzeichnen.
- Grundlagen von Schleifen in TypeScript: while, do-while, for, break und continue — Erfahren Sie, wie die while-, do-while- und for-Schleifen in TypeScript ihre Bedingungen auswerten, warum sich der Scope von Schleifenvariablen auf die Ergebnisse auswirkt und wie break und continue den Ablauf verändern.
- Erklärung der Gleichheit in JavaScript: ===, ==, relationale Operatoren und Object.is — Erfahren Sie, wie strenge und lockere Gleichheit, relationale Vergleiche sowie Object.is tatsächlich die Gleichartigkeit in JavaScript bestimmen, sowie welche Überführungstricks jede Methode verbirgt.