Den richtigen Typ wählen
Wenn du einen Typ entwirfst, entscheidest du, welche Daten dein Programm enthalten kann. Hier ist ein Truhentyp, so wie viele Programmierer ihn zuerst schreiben würden:
type Truhe = {
istVerschlossen: boolean;
schluesselName?: string; // nur für verschlossene Truhen
};
function oeffneTruhe(truhe: Truhe): string {
if (truhe.istVerschlossen) {
return `Du brauchst den Schlüssel ${truhe.schluesselName}`;
}
return "Die Truhe öffnet sich";
}
console.log(oeffneTruhe({ istVerschlossen: false }));
console.log(oeffneTruhe({ istVerschlossen: true }));Das gibt aus:
Die Truhe öffnet sich
Du brauchst den Schlüssel undefinedDer Kommentar sagt, dass nur verschlossene Truhen einen Schlüsselnamen haben, aber der Typ sagt es nicht. Der Typ erlaubt eine verschlossene Truhe ohne Schlüsselnamen, die niemand je öffnen könnte, und TypeScript hat nichts dagegen, wenn das Programm eine erzeugt. Die optionale Eigenschaft macht auch den restlichen Code umständlich: Selbst nach der Prüfung von truhe.istVerschlossen weiß TypeScript nur, dass truhe.schluesselName ein string | undefined ist, weil nichts im Typ die beiden verbindet. In diesem Zweig ergibt truhe.schluesselName.length error TS18048: 'truhe.schluesselName' is possibly 'undefined'.
Eine Union beschreibt die beiden Zustände genau:
type Truhe =
| { istVerschlossen: false }
| { istVerschlossen: true; schluesselName: string };true und false sind ebenfalls Literaltypen, deshalb kann eine boolean-Eigenschaft der Tag einer diskriminierten Union sein. Mit diesem Typ ist die verschlossene Truhe ohne Schlüsselnamen ein Fehler:
main.ts(13,25): error TS2345: Argument of type '{ istVerschlossen: true; }' is not assignable to parameter of type 'Truhe'.
Property 'schluesselName' is missing in type '{ istVerschlossen: true; }' but required in type '{ istVerschlossen: true; schluesselName: string; }'.Innerhalb von if (truhe.istVerschlossen) weiß TypeScript jetzt, dass die Truhe einen schluesselName hat, einen string, ohne undefined. Und auch eine unverschlossene Truhe mit Schlüsselnamen ist ein Fehler (TS2353).
Programmierer nennen das „unmögliche Zustände unmöglich machen“: Ein guter Typ erlaubt nur Daten, die Sinn ergeben. Achte in deinen Typen auf diese Anzeichen:
- ein
booleanmit optionalen Eigenschaften, die nur sinnvoll sind, wenn ertrueist, oder nur, wenn erfalseist, - mehrere optionale Eigenschaften, die immer entweder alle da sind oder alle fehlen,
- ein
status: stringmit Kommentaren darüber, welche Eigenschaften zu welchem Status gehören.
Jedes davon ist eine Union, die darauf wartet, geschrieben zu werden. Bei mehr als zwei Zuständen verwende statt eines boolean einen Texttag, etwa status: "kaempft" | "flieht" | "besiegt".
So schreibst du es:
Anweisungen
- 1
Das Programm gibt zweimal
undefinedaus, weil zwei Monster in der Liste nicht existieren können. Ersetze den TypMonsterdurch eine Union aus einem kämpfenden Monster,{ name: string; istBesiegt: false; leben: number }, und einem besiegten,{ name: string; istBesiegt: true; besiegtVon: string }. TypeScript meldet dann die zwei Monster. Behebe sie: Der Troll kämpft noch, mit40Leben, und der Zombie wurde vonBrunobesiegt. Das Programm gibt dann aus:Der Wolf hat 30 Leben Der Goblin wurde von Kira besiegt Der Troll hat 40 Leben Der Zombie wurde von Bruno besiegt - 2
Schreibe unter der Schleife eine Funktion
besiegemit den Parameternmonster(einMonster) undheld(ein Text) und dem RückgabetypMonster. Sie gibt ein neues, besiegtes Monster mit demselben Namen zurück, besiegt vonheld. (Ohne den Rückgabetyp würde TypeScript für das neue ObjektistBesiegt: booleanableiten, und das passt nicht zuMonster.) Gibbeschreibe(besiege(gegner[0], "Lena"))aus. Das Programm gibt dann aus:Der Wolf hat 30 Leben Der Goblin wurde von Kira besiegt Der Troll hat 40 Leben Der Zombie wurde von Bruno besiegt Der Wolf wurde von Lena besiegt