Ein Entwurf hat vielleicht noch keinen Titel, ein veröffentlichter Beitrag muss einen haben. Ergänzen Sie den Member, den Published neu deklariert, sodass headline kompiliert und das Programm HELLO ausgibt.
Beide Typen deklarieren port. Welchen Typ hat port am Kommentar?
type Defaults = { port: number; host: string };
type Secure = { port: 443 | 8443 };
function connect(config: Defaults & Secure) {
const port = config.port;
// here
return config.host + ":" + port;
}
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Ein Haustier, das zugleich Cat und Dog ist: Was meldet tsc?
type Cat = { kind: "cat"; lives: number };
type Dog = { kind: "dog"; bark: string };
type CatDog = Cat & Dog;
function describe(pet: CatDog) {
return pet.lives;
}
Anwenden · Aufgabe 7 von 7
Kleine Aufgabe. Schreiben Sie ein Interface Contact mit einem name und einer optionalen email sowie ein Interface Customer, das es erweitert, email zur Pflicht macht und eine customerNo (eine Zahl) hinzufügt. Schreiben Sie card(c), deren Parameter ein Customer ist, mit & kombiniert mit { note: string }, und die einen Text wie Ada <ada@example.com> #17: prefers email zurückgibt. Rufen Sie sie einmal auf. Lassen Sie dann Customer email: number deklarieren und lesen Sie den Fehler, und probieren Sie auch { note: number } & { note: string } aus. Prüfen Sie es im Editor oder mit npx tsc und node main.ts.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Tickets, die aufeinander aufbauen
Bug erweitert Ticket, fügt severity hinzu und verengt status auf zwei Werte. AssignedBug erweitert zwei Interfaces zugleich. line kombiniert AssignedBug direkt im Parameter per & mit Timestamped. shortLine nimmt jedes Ticket, also passt auch ein Bug. Closed bildet die Intersection von Ticket mit einem engeren status. Führen Sie npx tsc aus, dann node main.ts. Geben Sie Bug dann den Member status: number, oder ändern Sie Timestamped zu { createdAt: number }, und vergleichen Sie, wo tsc jeweils meldet.
main.ts
// The fields every ticket has.
interface Ticket {
readonly id: number;
title: string;
status: string;
}
// extends adds severity, and makes status more specific: still a string.
interface Bug extends Ticket {
status: "open" | "fixed";
severity: 1 | 2 | 3;
}
// Several bases at once.
interface Owned {
owner: string;
}
interface AssignedBug extends Bug, Owned {}
// & combines types where you need them, without a new interface.
type Timestamped = { createdAt: string };
function line(bug: AssignedBug & Timestamped): string {
return "#" + bug.id + " " + bug.title + " [" + bug.status + ", sev " + bug.severity + "] " + bug.owner + ", " + bug.createdAt;
}
// Any Bug is also a Ticket, so it fits where a Ticket is expected.
function shortLine(ticket: Ticket): string {
return "#" + ticket.id + " " + ticket.status;
}
const crash: AssignedBug & Timestamped = {
id: 7,
title: "Crash on save",
status: "open",
severity: 1,
owner: "Ada",
createdAt: "2026-10-04"
};
console.log(line(crash));
console.log(shortLine(crash));
// Both sides declare status: the intersection keeps what fits both.
type Closed = Ticket & { status: "fixed" | "wontfix" };
const old: Closed = { id: 3, title: "Typo in footer", status: "wontfix" };
console.log(shortLine(old));
Ausführen mit
npx tsc
node main.ts
Ausgabe
#7 Crash on save [open, sev 1] Ada, 2026-10-04
#7 open
#3 wontfix
status: "open" | "fixed" passt zu status: string, also darf Bug die Eigenschaft neu deklarieren.
crash ist ein AssignedBug & Timestamped und passt trotzdem zu shortLine, das nur ein Ticket will.
In Closed ist status "fixed" | "wontfix": der Teil von string, den beide Seiten erlauben.
status: number in Bug wäre error TS2430 bei Bug; createdAt: number in Timestamped zeigte sich erst bei crash, als never.
Ändern und ausführen
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übungen
Übung 1 von 2
Admins sind Konten mit mehr Regeln
Admin erweitert Account, deklariert aber plan: number, also meldet tsc error TS2430: Interface 'Admin' incorrectly extends interface 'Account'. Der plan eines Admins ist immer "team" oder "enterprise": Deklarieren Sie plan neu mit dieser Literal-Union, die zum string von Account passt. Die Zeile mit // @ts-expect-error findet dann weiterhin ihren Fehler, weil "free" nicht erlaubt ist. Lassen Sie danach canDelete zurückgeben, ob die permissions des Admins "delete" enthalten. Führen Sie die Tests aus: Der erste ist die Typprüfung.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Ein Interface, das ein anderes erweitert, darf eine geerbte Eigenschaft neu deklarieren, wenn der neue Typ zum alten passt.
Hinweis 2
"team" | "enterprise" ist eine Union von String-Literalen und passt daher zu string: plan: "team" | "enterprise";
Hinweis 3
Arrays haben includes: return admin.permissions.includes("delete");
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {summary, canDelete} from './main.ts';
const ada = {id: 1, email: 'ada@example.com', plan: 'team' as const, permissions: ['read', 'delete']};
const bob = {id: 2, email: 'bob@example.com', plan: 'enterprise' as const, permissions: ['read']};
test('summary zeigt die E-Mail, den Plan und die Zahl der Berechtigungen', () => {
const got = summary(ada);
assert.equal(got, 'ada@example.com (team, 2 permissions)', `summary(ada) ergab ${JSON.stringify(got)}`);
});
test('ein Admin mit der Berechtigung delete darf löschen', () => {
assert.equal(canDelete(ada), true, 'canDelete(ada) sollte true sein: ada hat "delete"');
});
test('ein Admin ohne sie darf es nicht', () => {
assert.equal(canDelete(bob), false, 'canDelete(bob) sollte false sein: bob hat nur "read"');
});
npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.
Programm ausführen:
npx tsc
node main.ts
Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):
Doc kombiniert drei Typen mit &. Timestamped sagt, createdAt ist ein String, Versioned sagt, eine Zahl, also ist createdAt never, und notes ist error TS2322: Type 'string' is not assignable to type 'never'. Ersetzen Sie in Versioned createdAt durch savedAt, einen String: wann die aktuelle Version gespeichert wurde. Geben Sie notes das savedAt 2026-10-03. label muss Notes v2 (created 2026-10-01, saved 2026-10-03) zurückgeben und bump(doc, savedAt) ein neues Doc mit um eins erhöhter Version und dem übergebenen savedAt.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
Hinweise
Hinweis 1
Der Fehler steht bei notes, aber die Ursache liegt in den Typen: Welche Eigenschaft deklarieren Timestamped und Versioned beide?
Hinweis 2
type Versioned = { version: number; savedAt: string }; dann savedAt: "2026-10-03" zu notes hinzufügen.
Hinweis 3
Setzen Sie in bump savedAt in den Spread hinter version: { ...doc, version: doc.version + 1, savedAt }
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {label, bump, notes} from './main.ts';
test('label zeigt die Version und wann das Dokument erstellt und gespeichert wurde', () => {
const got = label(notes);
assert.equal(got, 'Notes v2 (created 2026-10-01, saved 2026-10-03)', `label(notes) ergab ${JSON.stringify(got)}`);
});
test('bump erhöht die Version und hält fest, wann gespeichert wurde', () => {
const got = bump(notes, '2026-10-04');
assert.deepEqual(
got,
{title: 'Notes', version: 3, createdAt: '2026-10-01', savedAt: '2026-10-04'},
`bump(notes, '2026-10-04') ergab ${JSON.stringify(got)}`
);
});
test('bump lässt das ursprüngliche Dokument unverändert', () => {
bump(notes, '2026-10-04');
assert.equal(notes.version, 2, `notes.version ist nach bump ${notes.version}, aber bump sollte ein neues Dokument zurückgeben`);
});
npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.
Programm ausführen:
npx tsc
node main.ts
Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):
Eine Preview muss immer noch ein Article sein, und ein Article hat immer einen body. body?: string erlaubt undefined, string aber nicht, also nennt tsc die Eigenschaft in den nächsten Zeilen: Types of property 'body' are incompatible. extends kann eine Eigenschaft nur spezieller machen. Hat eine Vorschau wirklich keinen body, erweitern Sie Article nicht: Deklarieren Sie den gemeinsamen Teil als eigenes Interface, etwa interface Titled { title: string }, und lassen Sie beide dieses erweitern.
Ein Konflikt in einer Intersection, weit weg von seiner Ursache gefunden
type Product = { name: string; price: number };
type Listing = { name: string; price: string };
type Offer = Product & Listing;
const offer: Offer = { name: "Lamp", price: 25 };
Was tsc oder Node.js ausgibt
main.ts(5,38): error TS2322: Type 'number' is not assignable to type 'never'.
Warum, und die Lösung
tsc akzeptiert type Offer, denn & meldet nie einen Konflikt. price muss zugleich eine Zahl und ein String sein, also ist sein Typ never, und jeder Wert scheitert dort, wo er entsteht. Sehen Sie never in einer Meldung über eine Eigenschaft, schauen Sie sich die Typen an, die mit & kombiniert wurden. Entscheiden Sie, welcher Typ stimmt, und ändern Sie den anderen, oder schreiben Sie die Typen als Interfaces mit extends, damit tsc den Konflikt bei der Deklaration meldet.
Eine Union erweitern
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
interface Tagged extends Shape {
id: string;
}
Was tsc oder Node.js ausgibt
main.ts(3,26): error TS2312: An interface can only extend an object type or intersection of object types with statically known members.
Warum, und die Lösung
Ein Interface braucht eine bekannte Menge von Membern, und eine Union besteht aus mehreren Formen. Nehmen Sie stattdessen &: type Tagged = Shape & { id: string }. Das ist ein Kreis mit id oder ein Quadrat mit id, und das Verengen über kind funktioniert weiterhin.
TypeScript im Browser: der Compiler von TypeScript 6.0.3, Apache-2.0, danach die eigene Engine Ihres Browsers. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.
Kernideen
extends darf eine Eigenschaft verengen, ihr aber nie widersprechen
B2.4 hat gezeigt, dass interface Bug extends Ticket die Member von Ticket in Bug übernimmt. Bug darf einen davon auch neu deklarieren, solange der neue Typ zum alten passt: Aus status: string kann status: "open" | "fixed" werden, aus einem optionalen title?: string ein Pflichtfeld title: string. Jeder Bug ist dann immer noch ein Ticket und passt überall, wo ein Ticket erwartet wird. Die umgekehrte Richtung ist error TS2430: Interface 'Bug' incorrectly extends interface 'Ticket'., gefolgt von Zeilen, die die Eigenschaft nennen. Bei mehreren Basen, extends A, B, müssen diese sich über gemeinsame Eigenschaften einig sein, sonst meldet tsc TS2320: Interface 'C' cannot simultaneously extend types 'A' and 'B'. Ein Interface kann einen Objekttyp-Alias erweitern, aber keine Union: TS2312.
& bildet die Schnittmenge der Typen gemeinsamer Eigenschaften
A & B hat jede Eigenschaft von A und von B, und die Prüfung auf überzählige Eigenschaften gilt weiterhin. Deklarieren beide Seiten dieselbe Eigenschaft, wählt & nicht eine aus, wie es ein Spread täte: Die Eigenschaft bekommt beide Typen zugleich. { port: number } & { port: 443 | 8443 } gibt port den Typ 443 | 8443. Können sich die Typen nicht überschneiden, ist das Ergebnis never, ein Typ ohne Werte: In { name: string } & { name: number } ist name never, und { name: "Ada" } ist error TS2322: Type 'string' is not assignable to type 'never'. Widersprechen sich Literaltypen, etwa kind: "cat" und kind: "dog", reduziert tsc die ganze Intersection auf never.
Wo ein Konflikt auftaucht, und was Sie wählen
Das Handbook: „The principal difference between the two is how conflicts are handled“. extends prüft das neue Interface dort, wo Sie es deklarieren, gegen seine Basen; ein Konflikt wird also in der Interface-Zeile gemeldet. & beschwert sich nie beim Typalias: Der Konflikt wird zu never und zeigt sich erst später, wo ein Wert entsteht oder eine Eigenschaft benutzt wird, oft weit entfernt und mit einer rätselhaften Meldung wie Property 'lives' does not exist on type 'never'. Nehmen Sie also interface … extends für benannte Typen, die aufeinander aufbauen und bei denen Sie diese frühe Prüfung wollen. Nehmen Sie &, um Typen dort zu kombinieren, wo Sie sie verwenden, oder wenn eine Seite kein Interface ist, etwa eine Union.