Aufwärmen · Aufgabe 1 von 7
Zum Aufwärmen: Ein Parameter ist als id: number | string deklariert. Was sagt dieser Typ?
function printId(id: number | string) {
console.log("Your ID is: " + id);
}// B3.1 · ca. 30 Min. · Einstieg
Nach dieser Lektion sehen Sie einer Funktion mit Union-Typ an, welche Zeile der Compiler ablehnt und warum, und Sie schreiben ein switch, das nicht mehr kompiliert, sobald ein neuer Fall hinzukommt.
Lektion 1 von 5 in B3 Eingrenzung (Narrowing)
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
function printId(id: number | string) {
console.log("Your ID is: " + id);
}Vorhersagen · Aufgabe 2 von 7
function size(value: string[] | null) {
if (typeof value === "object") {
return value.length;
}
return 0;
}Üben · Aufgabe 3 von 7
type Fish = { swim: () => void };
type Bird = { fly: () => void };
interface Circle { kind: "circle"; radius: number }
interface Square { kind: "square"; sideLength: number }
type Shape = Circle | Square;Üben · Aufgabe 4 von 7
function handle(a: any, b: unknown) {
a.trim(); // line 2
const n: number = a; // line 3
b.trim(); // line 4
if (typeof b === "string") b.trim(); // line 5
}Üben · Aufgabe 5 von 7
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: ____ = shape;
return _exhaustiveCheck;
}
}Denksport · Aufgabe 6 von 7
function example(x: string | number, y: string | boolean) {
if (x === y) {
// what is the type of x here?
}
}Anwenden · Aufgabe 7 von 7
Prüfen Sie Ihr Ergebnis anhand dieser Liste
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
show nimmt string | number | null und entfernt mit jeder Prüfung ein Mitglied: zuerst === null, dann typeof. area macht ein switch über die Eigenschaft kind einer Discriminated Union, mit einer never-Prüfung im default-Zweig. toCount nimmt unknown, wie bei Ergebnissen von JSON.parse, und nutzt nur, was es geprüft hat. Führen Sie npx tsc aus: Keine Ausgabe heißt keine Fehler. Dann node main.ts. Fügen Sie Shape einmal | { kind: "triangle"; base: number } hinzu und starten Sie tsc erneut: Die never-Zeile wird gemeldet.
main.ts
// Three kinds of narrowing on one page. tsc follows each check.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rect"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2; // here shape is the circle member
case "rect":
return shape.width * shape.height; // here shape is the rect member
default: {
const unhandled: never = shape; // a new kind without a case fails here
return unhandled;
}
}
}
function show(value: string | number | null): string {
if (value === null) return "nothing"; // === null removes null
if (typeof value === "number") return value.toFixed(1); // value is number
return value.toUpperCase(); // only string is left
}
function toCount(input: unknown): number {
if (typeof input === "number") return input;
if (typeof input === "string" && input.trim() !== "") return Number(input);
return 0; // anything else: null, objects, empty text
}
console.log(show(null), show(2.5), show("ok"));
console.log(area({ kind: "circle", radius: 1 }).toFixed(2));
console.log(area({ kind: "rect", width: 2, height: 3 }));
console.log(toCount(JSON.parse("4")), toCount(JSON.parse('"7"')), toCount(JSON.parse("null")));
Ausführen mit
npx tsc
node main.tsAusgabe
nothing 2.5 OK
3.14
6
4 7 0Tab 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.
Übung 1 von 2
formatAmount nimmt number | string | null. Text behandelt die Funktion schon, aber tsc meldet error TS18047: 'amount' is possibly 'null', und formatAmount(null) stürzt ab. Lassen Sie sie für null "no amount" zurückgeben und behalten Sie die übrigen Ergebnisse bei. Starten Sie die Tests: 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.
Die Meldung zeigt auf das letzte return: Nach dem typeof-Zweig ist amount dort noch number | null.
Eine Gleichheitsprüfung entfernt null: if (amount === null) { … } am Anfang der Funktion.
Nehmen Sie dafür nicht typeof amount === "object": Das klappt hier nur zufällig, weil typeof null "object" ist.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export function formatAmount(amount: number | string | null): string {
if (amount === null) {
return "no amount";
}
if (typeof amount === "string") {
return Number(amount).toFixed(2) + " EUR";
}
return amount.toFixed(2) + " EUR";
}
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 formatAmount(amount: number | string | null): string {
if (typeof amount === "string") {
return Number(amount).toFixed(2) + " EUR";
}
return amount.toFixed(2) + " EUR";
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {formatAmount} from './main.ts';
test('formatAmount(4.5) ist "4.50 EUR"', () => {
assert.equal(formatAmount(4.5), '4.50 EUR', 'formatAmount(4.5) sollte "4.50 EUR" sein');
});
test('formatAmount("12") ist "12.00 EUR"', () => {
assert.equal(formatAmount('12'), '12.00 EUR', 'formatAmount("12") sollte "12.00 EUR" sein');
});
test('formatAmount(null) ist "no amount"', () => {
assert.equal(formatAmount(null), 'no amount', 'formatAmount(null) sollte "no amount" sein');
});
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 2
Payment hat ein drittes Mitglied bekommen, { method: "invoice"; days: number }, und tsc meldet jetzt die never-Zeile in describePayment. Ergänzen Sie den fehlenden Fall, sodass eine Rechnung über 30 Tage "invoice, due in 30 days" ergibt. Behalten Sie die never-Prüfung im default-Zweig.
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.
Die Meldung sagt, welches Mitglied im default-Zweig ankommt: { method: "invoice"; days: number; } ist never nicht zuweisbar.
Fügen Sie vor default ein case "invoice": ein. Darin ist p das invoice-Mitglied, also ist p.days eine Zahl.
return "invoice, due in " + p.days + " days";
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export type Payment =
| { method: "card"; last4: string }
| { method: "paypal"; email: string }
| { method: "invoice"; days: number };
export function describePayment(p: Payment): string {
switch (p.method) {
case "card":
return "card ending " + p.last4;
case "paypal":
return "PayPal " + p.email;
case "invoice":
return "invoice, due in " + p.days + " days";
default: {
const unhandled: never = p;
return unhandled;
}
}
}
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 type Payment =
| { method: "card"; last4: string }
| { method: "paypal"; email: string }
| { method: "invoice"; days: number };
export function describePayment(p: Payment): string {
switch (p.method) {
case "card":
return "card ending " + p.last4;
case "paypal":
return "PayPal " + p.email;
default: {
const unhandled: never = p;
return unhandled;
}
}
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {describePayment} from './main.ts';
test('Eine Kartenzahlung nennt die letzten vier Ziffern', () => {
assert.equal(describePayment({method: 'card', last4: '4242'}), 'card ending 4242', 'die Kartenzahlung sollte "card ending 4242" ergeben');
});
test('Eine PayPal-Zahlung nennt die E-Mail-Adresse', () => {
assert.equal(describePayment({method: 'paypal', email: 'ada@example.com'}), 'PayPal ada@example.com', 'die PayPal-Zahlung sollte "PayPal ada@example.com" ergeben');
});
test('Eine Rechnung nennt die Tage bis zur Fälligkeit', () => {
assert.equal(describePayment({method: 'invoice', days: 30}), 'invoice, due in 30 days', 'die Rechnung sollte "invoice, due in 30 days" ergeben');
});
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 herunterladenfunction count(items: string[] | null): number {
if (typeof items === "object") {
return items.length;
}
return 0;
}
console.log(count(["a", "b"]));
Was tsc oder Node.js ausgibt
main.ts(3,12): error TS18047: 'items' is possibly 'null'.Warum, und die Lösung
typeof null ist in JavaScript "object", also lässt die Prüfung string[] und null übrig, und tsc sagt das. Vergleichen Sie stattdessen mit null: if (items !== null), oder items != null, um auch undefined zu entfernen. Eine Truthiness-Prüfung, if (items), klappt hier auch, würde in anderen Unions aber auch einen leeren String oder 0 überspringen.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; side: number };
function area(shape: Shape): number {
return Math.PI * shape.radius ** 2;
}
console.log(area({ kind: "circle", radius: 1 }));
Was tsc oder Node.js ausgibt
main.ts(6,26): error TS2339: Property 'radius' does not exist on type 'Shape'.Warum, und die Lösung
shape könnte das Quadrat sein, und das hat kein radius; eine Union erlaubt nur, was jedes Mitglied erlaubt. Prüfen Sie zuerst die Diskriminante: if (shape.kind === "circle") return Math.PI * shape.radius ** 2; und behandeln Sie dann "square". Ein switch über shape.kind mit never-Prüfung im default lässt tsc Sie an jede noch nicht behandelte Art erinnern.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Animal = Fish | Bird;
function move(animal: Animal) {
if (animal.swim) {
animal.swim();
}
}
move({ swim: () => console.log("swimming") });
Was tsc oder Node.js ausgibt
main.ts(6,14): error TS2339: Property 'swim' does not exist on type 'Animal'.Warum, und die Lösung
In JavaScript prüft man eine Methode oft mit if (animal.swim), doch TypeScript lehnt das Lesen von swim ab, solange nicht feststeht, dass animal ein Fish ist: Bird hat kein swim. Der in-Operator ist die Prüfung, die TypeScript versteht: if ("swim" in animal) grenzt animal auf Fish ein, und animal.swim() kompiliert. Im else-Zweig ist animal ein Bird.
TypeScript im Browser: der Compiler von TypeScript 6.0.3, Apache-2.0, danach die eigene Engine Ihres Browsers. Lizenz und Quellcode
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.