Quest States
A discriminated union is good not only for different kinds of things, but also for the different states of one thing. A quest in Kira's log can be open, done, or failed, and each state has its own data: a done quest has a reward, a failed one has the reason why it failed, and an open one has neither:
type Quest =
| { status: "open"; title: string }
| { status: "done"; title: string; reward: number }
| { status: "failed"; title: string; reason: string };The tag here is called status, because that's what it describes. The name is up to you; it just has to be the same property in every member of the union, with a different literal type in each. A union this long is easier to read on several lines. Then you can write a | in front of each member, the first one too: TypeScript ignores a bar at the very beginning.
The type says exactly what you know about each quest. Only a done quest has a reward, so reading quest.reward without a check is an error:
type Quest =
| { status: "open"; title: string }
| { status: "done"; title: string; reward: number }
| { status: "failed"; title: string; reason: string };
const quests: Quest[] = [
{ status: "done", title: "Wolves in the Woods", reward: 50 },
{ status: "open", title: "The Lost Crown" },
{ status: "failed", title: "Save the Miller", reason: "the mill burned down" },
];
for (const quest of quests) {
console.log(`${quest.title}: ${quest.reward} gold`);
}main.ts(13,42): error TS2339: Property 'reward' does not exist on type 'Quest'.
Property 'reward' does not exist on type '{ status: "open"; title: string; }'.The second line of the message even says which quest has no reward: the open one. And a quest that makes no sense can't be written at all: const lostCrown: Quest = { status: "open", title: "The Lost Crown", reward: 100 }; gives error TS2353: Object literal may only specify known properties, and 'reward' does not exist in type '{ status: "open"; title: string; }'.
The property title is in all three members, so you can read it on any quest. For the rest, check the status first:
for (const quest of quests) {
if (quest.status === "done") {
console.log(`${quest.title}: ${quest.reward} gold`);
} else {
console.log(`${quest.title}: no reward`);
}
}This prints:
Wolves in the Woods: 50 gold
The Lost Crown: no reward
Save the Miller: no rewardCompare this with a single type with optional properties, { status: string; title: string; reward?: number; reason?: string }. It allows an open quest with a reward, a done quest without one, and even a status "dnoe". The next exercise is about such types.
Instructions
Run the program: the third quest doesn't fit the type. Add a third member to the union Quest: a failed quest, with status: "failed", a title and a reason (both texts).
Write a function describeQuest with a parameter quest of the type Quest that returns a description of the quest. Use switch on quest.status (with an exhaustiveness check in default, for practice), and print the description of every quest. The program then prints:
Wolves in the Woods: done, +50 gold
The Lost Crown: open
Save the Miller: failed (the mill burned down)
The Goblin Bridge: done, +30 goldAdd up the rewards of the done quests, and print the total at the end:
Wolves in the Woods: done, +50 gold
The Lost Crown: open
Save the Miller: failed (the mill burned down)
The Goblin Bridge: done, +30 gold
Total reward: 80 goldStart programming for free
6/8