switch on kind
When a union has more than two kinds, chains of if and else if get long. JavaScript has a statement made for choosing by one value: switch. It's new for you, so here is how it works, first on a literal union of directions:
type Direction = "north" | "south" | "east" | "west";
function walk(direction: Direction): void {
switch (direction) {
case "north":
console.log("You walk to the mountains");
break;
case "east":
console.log("You walk to the sea");
break;
default:
console.log("You stay in the village");
break;
}
}
walk("east");
walk("west");This prints:
You walk to the sea
You stay in the villageswitch takes the value in the parentheses, here direction, and compares it with the value after each case, using ===. It jumps to the first case that matches and runs the statements below it. break ends the switch: the program continues after its closing brace. default catches every value that has no case of its own, like an else at the end of an if chain. It's optional, and it's usually written last.
Watch out for break. Without it, the program doesn't stop at the next case: it falls through and runs the statements of the following cases too, until it reaches a break or the end of the switch. With the items from the previous exercise:
function useItem(item: Item): void {
switch (item.kind) {
case "potion":
console.log(`You drink the ${item.name}`);
case "weapon":
console.log(`You draw the ${item.name}`);
break;
}
}
useItem({ kind: "potion", name: "Healing Potion", heal: 20 });This prints:
You drink the Healing Potion
You draw the Healing PotionTypeScript doesn't report a missing break, so when a switch does more than it should, look for one.
In a function, return ends the switch too (it ends the whole function), so a case that returns needs no break. And on a discriminated union, every case narrows the type, just like an if:
function price(item: Item): number {
switch (item.kind) {
case "potion":
return item.heal * 2;
case "weapon":
return item.damage * 5;
}
}
for (const item of inventory) {
console.log(`${item.name}: ${price(item)} gold`);
}With the inventory from the previous exercise, this prints:
Healing Potion: 40 gold
Silver Sword: 60 goldIn case "potion":, the item is a Potion, so item.heal is allowed; item.damage there would be error TS2339: Property 'damage' does not exist on type 'Potion'. Notice also that there's no return after the switch, and TypeScript doesn't mind: it knows that the two cases cover every kind of Item. More about that in the next exercise.
TypeScript also checks the values after case. With a typo:
type Potion = { kind: "potion"; name: string; heal: number };
type Weapon = { kind: "weapon"; name: string; damage: number };
type Item = Potion | Weapon;
function useItem(item: Item): void {
switch (item.kind) {
case "poton":
console.log(`You drink the ${item.name}`);
break;
case "weapon":
console.log(`You draw the ${item.name}`);
break;
}
}main.ts(7,14): error TS2678: Type '"poton"' is not comparable to type '"potion" | "weapon"'.
main.ts(8,47): error TS2339: Property 'name' does not exist on type 'never'.The first error says that "poton" can't be the kind of an item. The second one is its consequence: in a case that can never match, the item has the type never, the type of a value that can't exist. You'll meet never again in the next exercise.
Instructions
Rewrite describeItem with switch (item.kind), with one case for each kind that returns its description. The program prints the same two lines as before.
Kira found a key. Add a third kind of item, type Key = { kind: "key"; name: string; opens: string };, to the union Item, add a case for it to the switch, and add { kind: "key", name: "Old Key", opens: "the crypt" } to the end of the inventory. The program then prints:
Healing Potion (heals 20)
Silver Sword (damage 12)
Old Key (opens the crypt)Start programming for free
2/8