Состояния квеста
Размеченное объединение хорошо подходит не только для разных видов вещей, но и для разных состояний одной вещи. Квест в журнале Киры может быть открытым, выполненным или проваленным, и у каждого состояния свои данные: у выполненного квеста есть награда, у проваленного — причина провала, а у открытого нет ни того, ни другого:
type Quest =
| { status: "open"; title: string }
| { status: "done"; title: string; reward: number }
| { status: "failed"; title: string; reason: string };Здесь тег называется status, потому что именно это он описывает. Имя выбираете вы; главное, чтобы это было одно и то же свойство в каждом члене объединения, с разным литеральным типом в каждом. Длинное объединение удобнее читать в несколько строк. Тогда перед каждым членом можно писать |, перед первым тоже: TypeScript игнорирует черту в самом начале.
Тип говорит ровно то, что вы знаете о каждом квесте. Награда есть только у выполненного, поэтому чтение quest.reward без проверки — ошибка:
type Quest =
| { status: "open"; title: string }
| { status: "done"; title: string; reward: number }
| { status: "failed"; title: string; reason: string };
const quests: Quest[] = [
{ status: "done", title: "Волки в лесу", reward: 50 },
{ status: "open", title: "Потерянная корона" },
{ status: "failed", title: "Спасти мельника", reason: "мельница сгорела" },
];
for (const quest of quests) {
console.log(`${quest.title}: ${quest.reward} золота`);
}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; }'.Вторая строка сообщения даже говорит, у какого квеста нет награды: у открытого. А бессмысленный квест вообще нельзя записать: const lostCrown: Quest = { status: "open", title: "Потерянная корона", reward: 100 }; даёт error TS2353: Object literal may only specify known properties, and 'reward' does not exist in type '{ status: "open"; title: string; }'.
Свойство title есть во всех трёх членах, поэтому читать его можно у любого квеста. Для остального сначала проверьте статус:
for (const quest of quests) {
if (quest.status === "done") {
console.log(`${quest.title}: ${quest.reward} золота`);
} else {
console.log(`${quest.title}: без награды`);
}
}Программа выведет:
Волки в лесу: 50 золота
Потерянная корона: без награды
Спасти мельника: без наградыСравните с одним типом с необязательными свойствами: { status: string; title: string; reward?: number; reason?: string }. Он допускает открытый квест с наградой, выполненный квест без неё и даже статус "dnoe". Таким типам посвящено следующее упражнение.
Задание
- 1
Запустите программу: третий квест не подходит под тип. Добавьте третий член в объединение
Quest: проваленный квест сstatus: "failed",titleиreason(оба — тексты). - 2
Напишите функцию
describeQuestс параметромquestтипаQuest, которая возвращает описание квеста. Используйтеswitchпоquest.status(для практики — с проверкой полноты вdefault) и выведите описание каждого квеста. Программа выведет:Волки в лесу: выполнено, награда: 50 Потерянная корона: активно Спасти мельника: провалено (мельница сгорела) Мост гоблинов: выполнено, награда: 30 - 3
Сложите награды за выполненные квесты и выведите сумму в конце:
Волки в лесу: выполнено, награда: 50 Потерянная корона: активно Спасти мельника: провалено (мельница сгорела) Мост гоблинов: выполнено, награда: 30 Общая награда: 80