Aufwärmen · Aufgabe 1 von 7
// B2.5 · ca. 38 Min. · Einstieg
Literaltypen und as const; Projekt: ein typisiertes Adressbuch
Nach dieser Lektion erlauben Sie nur wenige exakte Werte, sagen voraus, wann TypeScript ein Literal erweitert, und bauen ein typisiertes Adressbuch.
Lektion 5 von 5 in B2 Alltägliche Typen
Danach können Sie
- Literaltypen und Unions von Literalen schreiben und die Fehler lesen, die tsc für andere Werte meldet
- Vorhersagen, wann TypeScript einen Literaltyp erweitert, und ihn mit as "GET" oder as const behalten
- Ein typisiertes Adressbuch mit Interface, optionaler Eigenschaft, Literal-Union und as-const-Liste bauen
Vorhersagen · Aufgabe 2 von 7
Sagen Sie es vorher, bevor Sie weiterlesen: alignment nimmt nur drei exakte Strings an. Was meldet tsc?
function printText(s: string, alignment: "left" | "right" | "center") { console.log(s + " (" + alignment + ")"); } printText("Hello, world", "left"); printText("G'day, mate", "centre");Üben · Aufgabe 3 von 7
Ergänzen Sie die zwei Wörter hinter dem Objektliteral, die method als Literaltyp "GET" behalten, damit tsc den Aufruf akzeptiert.
function handleRequest(url: string, method: "GET" | "POST") { console.log(method + " " + url); } const req = { url: "https://example.com", method: "GET" } ____; handleRequest(req.url, req.method);const req = { url: "https://example.com", method: "GET" } ;Üben · Aufgabe 4 von 7
Welchen Typ leitet TypeScript für greeting ab?
const greeting = "Hello"; let other = "Hello";Üben · Aufgabe 5 von 7
Ordnen Sie jeder Deklaration den Typ zu, den TypeScript ableitet.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. settings ist eine const. Was meldet tsc für Zeile 2?
const settings = { theme: "dark" }; settings.theme = "light"; console.log(settings.theme);Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Deklarieren Sie type Theme = "light" | "dark" und eine Funktion background(theme: Theme), die für dark "#222222" und sonst "#ffffff" zurückgibt. Speichern Sie eine Einstellung als Objekt { user: "ada", theme: "dark" } und übergeben Sie saved.theme an background. Lesen Sie zuerst den Fehler von tsc und beheben Sie ihn dann mit as const. Probieren Sie zum Schluss background("blue") aus.
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
Eine Preisliste mit ausgerichteten Spalten
pad nimmt als Ausrichtung nur "left" oder "right" an. Die Einstellungen jeder Spalte stehen in einem Objekt mit as const, also behält align seinen Literaltyp und kann an pad übergeben werden. total ist ein let, also leitet TypeScript number ab, und es kann in der Schleife wachsen. Führen Sie npx tsc aus, dann node main.ts. Entfernen Sie danach ein as const und lesen Sie, was tsc sagt, oder übergeben Sie "middle" an pad.
main.ts
// Literal types: only these exact values fit.
type Align = "left" | "right";
function pad(text: string, width: number, align: Align): string {
return align === "left" ? text.padEnd(width, ".") : text.padStart(width, ".");
}
// as const keeps "left" and "right" as literal types, not string.
const nameColumn = { width: 10, align: "left" } as const;
const priceColumn = { width: 8, align: "right" } as const;
function row(name: string, price: string): string {
return pad(name, nameColumn.width, nameColumn.align) + pad(price, priceColumn.width, priceColumn.align);
}
const items = [
{ name: "Tea", price: 2.5 },
{ name: "Cake", price: 3.75 },
];
let total = 0; // let: TypeScript infers number, so it can change
for (const item of items) {
console.log(row(item.name, item.price.toFixed(2)));
total += item.price;
}
console.log(row("Total", total.toFixed(2)));
Ausführen mit
npx tsc
node main.tsAusgabe
Tea...........2.50
Cake..........3.75
Total.........6.25- Ohne as const wäre nameColumn.align ein string, und pad(…, nameColumn.align) wäre error TS2345.
- items hat keine Annotation: TypeScript leitet { name: string; price: number; }[] aus den Literalen ab.
- Auch der Vergleich mit === in pad wird geprüft: align === "centre" wäre error TS2367.
- Die Literaltypen gibt es nur für tsc: Node.js entfernt sie, und die Ausgabe ist die von einfachem JavaScript.
Ä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 3
tsc den Tippfehler finden lassen
Dieses Programm soll Express: 9.90 EUR ausgeben, gibt aber 4.90 aus, und tsc meldet nichts: speed ist ein string, also wird der Tippfehler "expres" angenommen und still wie standard behandelt. Ändern Sie den Typ von speed in eine Union der beiden String-Literale "standard" und "express". Dann meldet tsc den Tippfehler: Beheben Sie ihn.
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
Eine Union von Literalen schreibt man mit | zwischen den Werten: "a" | "b".
Hinweis 2
Die erste Zeile wird zu export function shippingCost(speed: "standard" | "express"): number {
Hinweis 3
tsc meldet dann Argument of type '"expres"' in Zeile 5. Schreiben Sie "express".
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export function shippingCost(speed: "standard" | "express"): number {
return speed === "express" ? 9.9 : 4.9;
}
console.log("Express: " + shippingCost("express").toFixed(2) + " EUR");
console.log("Standard: " + shippingCost("standard").toFixed(2) + " EUR");
Auf dem eigenen Computer ausführen
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.
main.ts
export function shippingCost(speed: string): number {
return speed === "express" ? 9.9 : 4.9;
}
console.log("Express: " + shippingCost("expres").toFixed(2) + " EUR");
console.log("Standard: " + shippingCost("standard").toFixed(2) + " EUR");
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';
import {shippingCost} from './main.ts';
test('shippingCost("express") ist 9.9', () => {
assert.equal(shippingCost('express'), 9.9, 'shippingCost("express") sollte 9.9 sein');
});
test('shippingCost("standard") ist 4.9', () => {
assert.equal(shippingCost('standard'), 4.9, 'shippingCost("standard") sollte 4.9 sein');
});
test('das Programm gibt beide Preise aus', async () => {
const out = (await runMain()).trimEnd();
assert.equal(out, 'Express: 9.90 EUR\nStandard: 4.90 EUR', `das Programm gab ${JSON.stringify(out)} aus`);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
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.tsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
npx tsc
node --testlearnrun.js herunterladenÜbung 2 von 3
Die Methode als Literal behalten
request nimmt nur "GET" oder "POST" an, und das Programm gibt die richtigen Zeilen aus, aber tsc meldet zweimal Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'. Beheben Sie es dort, wo die Objekte entstehen, nicht in den Aufrufen und nicht in der Funktion: Behalten Sie jede Methode als Literaltyp.
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
Objekteigenschaften werden erweitert: home.method ist string, nicht "GET", weil sie noch zugewiesen werden könnte.
Hinweis 2
Zwei Wörter hinter der schließenden Klammer eines Objektliterals machen jede Eigenschaft zu ihrem Literaltyp.
Hinweis 3
Schreiben Sie const home = { url: "/home", method: "GET" } as const; und dasselbe für login.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export function request(url: string, method: "GET" | "POST"): string {
return method + " " + url;
}
const home = { url: "/home", method: "GET" } as const;
const login = { url: "/login", method: "POST" } as const;
console.log(request(home.url, home.method));
console.log(request(login.url, login.method));
Auf dem eigenen Computer ausführen
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.
main.ts
export function request(url: string, method: "GET" | "POST"): string {
return method + " " + url;
}
const home = { url: "/home", method: "GET" };
const login = { url: "/login", method: "POST" };
console.log(request(home.url, home.method));
console.log(request(login.url, login.method));
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';
import {request} from './main.ts';
test('request verbindet Methode und URL', () => {
assert.equal(request('/cart', 'POST'), 'POST /cart', 'request("/cart", "POST") sollte POST /cart sein');
});
test('das Programm gibt beide Anfragen aus', async () => {
const out = (await runMain()).trimEnd();
assert.equal(out, 'GET /home\nPOST /login', `das Programm gab ${JSON.stringify(out)} aus`);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
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.tsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
npx tsc
node --testlearnrun.js herunterladenÜbung 3 von 3
Projekt: ein typisiertes Adressbuch
Das Modulprojekt. Das Adressbuch unten läuft, aber tsc meldet viele Fehler, und die Funktionen sind leer. Deklarieren Sie type Kind ("friend", "work" oder "family") und interface Contact (name und email als Strings, ein optionales phone, kind als Kind, favorite als boolean). Machen Sie KINDS zu einer as-const-Liste und geben Sie ada einen Typ, damit tsc beide akzeptiert. Schreiben Sie dann die Funktionen: formatContact liefert * Ada Lovelace <ada@example.com> (work, no phone), mit "* " nur für Favoriten und der Telefonnummer, wenn es eine gibt; byKind behält die Kontakte einer Art; favorites liefert deren Namen. Die Tests prüfen jede Funktion und das ausgegebene Adressbuch.
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
Beginnen Sie mit den Typen: type Kind = "friend" | "work" | "family"; und einem interface Contact mit phone?: string.
Hinweis 2
Bei ada wird kind zu string erweitert, ebenso jedes Element von KINDS. Schreiben Sie const ada: Contact = … und setzen Sie as const hinter das KINDS-Array.
Hinweis 3
contact.phone ?? "no phone" liefert den Standardwert. byKind ist book.filter((contact) => contact.kind === kind); favorites filtert nach favorite und bildet dann auf den Namen ab.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
// A typed contact book. Declare the types, then finish the functions.
type Kind = "friend" | "work" | "family";
interface Contact {
name: string;
email: string;
phone?: string;
kind: Kind;
favorite: boolean;
}
export const KINDS = ["friend", "work", "family"] as const;
export function formatContact(contact: Contact): string {
const star = contact.favorite ? "* " : "";
const phone = contact.phone ?? "no phone";
return star + contact.name + " <" + contact.email + "> (" + contact.kind + ", " + phone + ")";
}
export function byKind(book: Contact[], kind: Kind): Contact[] {
return book.filter((contact) => contact.kind === kind);
}
export function favorites(book: Contact[]): string[] {
return book.filter((contact) => contact.favorite).map((contact) => contact.name);
}
export function summary(book: Contact[]): string {
const parts: string[] = [];
for (const kind of KINDS) {
parts.push(kind + ": " + byKind(book, kind).length);
}
return parts.join(", ");
}
const ada: Contact = { name: "Ada Lovelace", email: "ada@example.com", kind: "work", favorite: true };
const book: Contact[] = [
ada,
{ name: "Grace Hopper", email: "grace@example.com", phone: "+49 30 1234567", kind: "work", favorite: false },
{ name: "Linus Torvalds", email: "linus@example.com", kind: "friend", favorite: true },
];
for (const contact of book) {
console.log(formatContact(contact));
}
console.log(summary(book));
console.log("Favorites: " + favorites(book).join(", "));
Auf dem eigenen Computer ausführen
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.
main.ts
// A typed contact book. Declare the types, then finish the functions.
// 1. Kind: "friend", "work" or "family".
// 2. Contact: name and email (text), phone (optional text), kind (a Kind),
// favorite (true or false).
export const KINDS = ["friend", "work", "family"];
export function formatContact(contact: Contact): string {
// "* Ada Lovelace <ada@example.com> (work, no phone)"; only favorites start with "* "
return "";
}
export function byKind(book: Contact[], kind: Kind): Contact[] {
return [];
}
export function favorites(book: Contact[]): string[] {
// the names of every favorite, in book order
return [];
}
export function summary(book: Contact[]): string {
// "friend: 1, work: 2, family: 0", in the order of KINDS
const parts: string[] = [];
for (const kind of KINDS) {
parts.push(kind + ": " + byKind(book, kind).length);
}
return parts.join(", ");
}
const ada = { name: "Ada Lovelace", email: "ada@example.com", kind: "work", favorite: true };
const book: Contact[] = [
ada,
{ name: "Grace Hopper", email: "grace@example.com", phone: "+49 30 1234567", kind: "work", favorite: false },
{ name: "Linus Torvalds", email: "linus@example.com", kind: "friend", favorite: true },
];
for (const contact of book) {
console.log(formatContact(contact));
}
console.log(summary(book));
console.log("Favorites: " + favorites(book).join(", "));
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';
import {formatContact, byKind, favorites, summary} from './main.ts';
const ada = {name: 'Ada', email: 'ada@example.com', kind: 'work', favorite: true};
const bob = {name: 'Bob', email: 'bob@example.com', phone: '555-0100', kind: 'family', favorite: false};
const cy = {name: 'Cy', email: 'cy@example.com', kind: 'work', favorite: false};
const book = [ada, bob, cy];
test('formatContact markiert einen Favoriten ohne Telefon', () => {
const line = formatContact(ada);
assert.equal(line, '* Ada <ada@example.com> (work, no phone)', `formatContact(ada) lieferte ${JSON.stringify(line)}`);
});
test('formatContact zeigt das Telefon eines Kontakts, der kein Favorit ist', () => {
const line = formatContact(bob);
assert.equal(line, 'Bob <bob@example.com> (family, 555-0100)', `formatContact(bob) lieferte ${JSON.stringify(line)}`);
});
test('byKind behält nur die Kontakte dieser Art', () => {
const names = byKind(book, 'work').map((contact) => contact.name);
assert.deepEqual(names, ['Ada', 'Cy'], `byKind(book, 'work') lieferte ${JSON.stringify(names)}`);
assert.equal(byKind(book, 'friend').length, 0, 'byKind(book, "friend") sollte leer sein');
});
test('favorites liefert die Namen der Favoriten', () => {
const names = favorites(book);
assert.deepEqual(names, ['Ada'], `favorites(book) lieferte ${JSON.stringify(names)}`);
});
test('summary zählt jede Art', () => {
const text = summary(book);
assert.equal(text, 'friend: 0, work: 2, family: 1', `summary(book) lieferte ${JSON.stringify(text)}`);
});
test('das Programm gibt das ganze Adressbuch aus', async () => {
const out = (await runMain()).trimEnd();
assert.equal(
out,
'* Ada Lovelace <ada@example.com> (work, no phone)\nGrace Hopper <grace@example.com> (work, +49 30 1234567)\n* Linus Torvalds <linus@example.com> (friend, no phone)\nfriend: 1, work: 2, family: 0\nFavorites: Ada Lovelace, Linus Torvalds',
`das Programm gab ${JSON.stringify(out)} aus`
);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
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.tsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
npx tsc
node --testlearnrun.js herunterladenHäufige Fehler
Ein Wert knapp außerhalb der Literal-Union
function printText(s: string, alignment: "left" | "right" | "center") {
console.log(s);
}
printText("Price list", "centre");
Was tsc oder Node.js ausgibt
main.ts(5,25): error TS2345: Argument of type '"centre"' is not assignable to parameter of type '"center" | "left" | "right"'.Warum, und die Lösung
Eine Literal-Union erlaubt genau die Werte, die sie aufzählt, genau so geschrieben. tsc nennt den gefundenen Wert, "centre", und die Werte, die es annimmt. Schreiben Sie "center". Genau dafür ist eine Literal-Union da: In einfachem JavaScript liefe der Tippfehler und fiele still durch jede Prüfung auf "center".
Eine zu string erweiterte Eigenschaft
type Mode = "light" | "dark";
let mode: Mode = "light";
const saved = { mode: "dark" };
mode = saved.mode;
Was tsc oder Node.js ausgibt
main.ts(5,1): error TS2322: Type 'string' is not assignable to type 'Mode'.Warum, und die Lösung
saved.mode enthält "dark", aber sein Typ ist string: Eine Objekteigenschaft kann noch zugewiesen werden, also erweitert TypeScript sie. string ist weiter als Mode, also lehnt tsc ab. Behalten Sie das Literal dort, wo das Objekt entsteht: const saved = { mode: "dark" } as const;, oder annotieren Sie es: const saved: { mode: Mode } = { mode: "dark" };.
Eine Liste mit as const verändern
const KINDS = ["friend", "work"] as const;
KINDS.push("family");
Was tsc oder Node.js ausgibt
main.ts(2,7): error TS2339: Property 'push' does not exist on type 'readonly ["friend", "work"]'.Warum, und die Lösung
as const macht ein Array zu einem readonly-Tupel: genau diese Elemente, in dieser Reihenfolge, und keine Methode, die es verändert, etwa push. Für eine feste Liste von Arten ist das gewollt. Muss die Liste wachsen, ergänzen Sie "family" im Literal selbst, oder lassen Sie as const weg und annotieren Sie sie, etwa const kinds: string[] = ["friend", "work"].
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.