Diskriminierte Unions
Am Ende der vorigen Lektion hast du Tränke und Waffen an ihren Eigenschaften unterschieden, mit "heilung" in gegenstand. Das funktioniert, ist aber anfällig: TypeScript bemerkt einen Tippfehler wie "heilng" in gegenstand nicht, und wenn eines Tages eine andere Art von Gegenstand eine Eigenschaft heilung bekommt, hört die Prüfung stillschweigend auf zu funktionieren.
Der übliche Weg, in TypeScript „eines von mehreren Dingen“ zu modellieren, ist, jedem Objekt eine Eigenschaft zu geben, die sagt, was es ist. Sie heißt meist art, und in jedem Typ der Union hat sie einen anderen Literaltyp:
type Trank = { art: "trank"; name: string; heilung: number };
type Waffe = { art: "waffe"; name: string; schaden: number };
type Gegenstand = Trank | Waffe;
const inventar: Gegenstand[] = [
{ art: "trank", name: "Heiltrank", heilung: 20 },
{ art: "waffe", name: "Silberschwert", schaden: 12 },
];Eine solche Union heißt diskriminierte Union (englisch discriminated union): Alle ihre Typen haben die Eigenschaft art gemeinsam, und ihr Wert unterscheidet sie. Die Eigenschaft heißt auch Tag. Sie zu prüfen grenzt den Typ ein:
function beschreibe(gegenstand: Gegenstand): string {
if (gegenstand.art === "trank") {
return `${gegenstand.name} (heilt ${gegenstand.heilung})`;
} else {
return `${gegenstand.name} (Schaden ${gegenstand.schaden})`;
}
}
for (const gegenstand of inventar) {
console.log(beschreibe(gegenstand));
}Das gibt aus:
Heiltrank (heilt 20)
Silberschwert (Schaden 12)Innerhalb des if ist der Gegenstand ein Trank, weil nur Tränke art: "trank" haben; im else-Zweig ist er eine Waffe. Die Eigenschaften, die jeder Typ der Union hat, hier art und name, kannst du ohne Prüfung lesen.
Wenn das Programm läuft, ist der Tag eine gewöhnliche Eigenschaft: console.log(inventar[1]) gibt { art: "waffe", name: "Silberschwert", schaden: 12 } aus. Für TypeScript ist er dagegen der Schlüssel zum ganzen Objekt. TypeScript weiß, welche Eigenschaften zu welcher Art gehören, und prüft deshalb auch deine Daten:
type Trank = { art: "trank"; name: string; heilung: number };
type Waffe = { art: "waffe"; name: string; schaden: number };
type Gegenstand = Trank | Waffe;
const beute: Gegenstand[] = [
{ art: "trank", name: "Elixier", schaden: 5 },
{ art: "trnak", name: "Tonikum", heilung: 5 },
];main.ts(6,38): error TS2353: Object literal may only specify known properties, and 'schaden' does not exist in type 'Trank'.
main.ts(7,7): error TS2820: Type '"trnak"' is not assignable to type '"trank" | "waffe"'. Did you mean '"trank"'?Ein Trank mit Schaden wird abgefangen, weil ein Trank keinen schaden hat, ebenso eine falsch geschriebene Art, mit einem Vorschlag. Ein falsch geschriebener Vergleich wie gegenstand.art === "trnak" ergibt den Fehler TS2367, den du kennst: Die Typen haben keine Überschneidung.
Typische Fehler:
- Ein Objekt ohne seinen Tag.
const tonikum: Gegenstand = { name: "Tonikum", heilung: 5 };ergibterror TS2322: Type '{ name: string; heilung: number; }' is not assignable to type 'Gegenstand'., und die zweite Zeile der Meldung erklärt:Property 'art' is missing in type '{ name: string; heilung: number; }' but required in type 'Trank'. art: stringin den Typen statt eines Literaltyps. Dann kannartjeder Text sein, sie unterscheidet die Typen nicht, und die Prüfung grenzt nichts ein: Nachif (gegenstand.art === "trank")ist das Lesen vongegenstand.heilungimmer noch ein Fehler.
So schreibst du es:
Anweisungen
- 1
Die Belohnung für einen Auftrag ist entweder Gold oder ein Gegenstand. Die Liste im Editor verwendet schon beide Arten, aber TypeScript kennt
GegenstandBelohnungnoch nicht. Definiere sie für die Gegenstandsbelohnungen in der Liste:art: "gegenstand", einname(ein Text) und eineanzahl(eine Zahl). - 2
Schreibe eine Funktion
beschreibeBelohnungmit einem Parameterbelohnungvom TypBelohnung. Für Gold gibt sie zum Beispiel50 Goldzurück; für einen Gegenstand die Anzahl und den Namen, zum Beispiel3x Heiltrank. Gib jede Belohnung aus der Liste aus. Das Programm gibt dann aus:50 Gold 1x Silberschwert 3x Heiltrank 30 Gold - 3
Addiere das Gold aller Goldbelohnungen und gib die Summe nach den Belohnungen aus:
50 Gold 1x Silberschwert 3x Heiltrank 30 Gold Gold insgesamt: 80