switch по kind
Когда в объединении больше двух видов, цепочки if и else if становятся длинными. В JavaScript есть инструкция, созданная для выбора по одному значению: switch. Для вас она новая, поэтому сначала посмотрим, как она работает, на литеральном объединении направлений:
type Direction = "north" | "south" | "east" | "west";
function walk(direction: Direction): void {
switch (direction) {
case "north":
console.log("Вы идёте к горам");
break;
case "east":
console.log("Вы идёте к морю");
break;
default:
console.log("Вы остаётесь в деревне");
break;
}
}
walk("east");
walk("west");Программа выведет:
Вы идёте к морю
Вы остаётесь в деревнеswitch берёт значение в скобках (здесь direction) и сравнивает его через === со значением после каждого case. Он переходит к первому подходящему case и выполняет инструкции под ним. break завершает switch: программа продолжает работу после его закрывающей фигурной скобки. default ловит все значения, у которых нет своего case, — как else в конце цепочки if. Он необязателен, и обычно его пишут последним.
Осторожно с break. Без него программа не останавливается на следующем case: она проваливается (fall through) и выполняет инструкции следующих вариантов тоже, пока не дойдёт до break или до конца switch. На предметах из прошлого упражнения:
function useItem(item: Item): void {
switch (item.kind) {
case "potion":
console.log(`Вы выпиваете: ${item.name}`);
case "weapon":
console.log(`Вы достаёте: ${item.name}`);
break;
}
}
useItem({ kind: "potion", name: "Зелье лечения", heal: 20 });Программа выведет:
Вы выпиваете: Зелье лечения
Вы достаёте: Зелье леченияTypeScript не сообщает о пропущенном break, поэтому, если switch делает больше, чем нужно, ищите его.
В функции return тоже завершает switch (он завершает всю функцию), так что case, который возвращает значение, не нуждается в break. А на размеченном объединении каждый case сужает тип, как и 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)}`);
}С инвентарём из прошлого упражнения программа выведет:
Зелье лечения: цена 40
Серебряный меч: цена 60В case "potion": предмет — это Potion, поэтому item.heal разрешено; item.damage там было бы error TS2339: Property 'damage' does not exist on type 'Potion'. Обратите внимание и на то, что после switch нет return, и TypeScript не возражает: он знает, что два варианта покрывают все виды Item. Подробнее об этом — в следующем упражнении.
TypeScript проверяет и значения после case. Вот пример с опечаткой:
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(`Вы выпиваете: ${item.name}`);
break;
case "weapon":
console.log(`Вы достаёте: ${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'.Первая ошибка говорит, что "poton" не может быть видом предмета. Вторая — её следствие: в case, который никогда не сработает, у предмета тип never — тип значения, которого не может существовать. С never вы ещё встретитесь в следующем упражнении.
Задание
- 1
Перепишите
describeItemс помощьюswitch (item.kind): для каждого вида — свойcase, который возвращает описание. Программа выведет те же две строки, что и раньше. - 2
Кира нашла ключ. Добавьте третий вид предметов,
type Key = { kind: "key"; name: string; opens: string };, в объединениеItem, добавьте для негоcaseвswitchи добавьте{ kind: "key", name: "Старый ключ", opens: "склеп" }в конец инвентаря. Программа выведет:Зелье лечения (лечит 20) Серебряный меч (урон 12) Старый ключ (открывает склеп)