Выбор правильного типа
Проектируя тип, вы решаете, какие данные может хранить ваша программа. Вот тип сундука, каким его сначала пишут многие программисты:
type Chest = {
isLocked: boolean;
keyName?: string; // только у запертых сундуков
};
function openChest(chest: Chest): string {
if (chest.isLocked) {
return `Нужен ключ: ${chest.keyName}`;
}
return "Сундук открывается";
}
console.log(openChest({ isLocked: false }));
console.log(openChest({ isLocked: true }));Программа выведет:
Сундук открывается
Нужен ключ: undefinedКомментарий говорит, что название ключа есть только у запертых сундуков, а тип этого не говорит. Тип допускает запертый сундук без названия ключа, который никто никогда не откроет, и TypeScript не возражает, когда программа такой создаёт. Необязательное свойство делает неуклюжим и остальной код: даже проверив chest.isLocked, TypeScript знает про chest.keyName лишь то, что это string | undefined, потому что в типе ничто не связывает эти два свойства. В этой ветке chest.keyName.length даёт error TS18048: 'chest.keyName' is possibly 'undefined'.
Объединение описывает два состояния точно:
type Chest =
| { isLocked: false }
| { isLocked: true; keyName: string };true и false — тоже литеральные типы, поэтому свойство типа boolean может быть тегом размеченного объединения. С этим типом запертый сундук без названия ключа — ошибка:
main.ts(13,23): error TS2345: Argument of type '{ isLocked: true; }' is not assignable to parameter of type 'Chest'.
Property 'keyName' is missing in type '{ isLocked: true; }' but required in type '{ isLocked: true; keyName: string; }'.Внутри if (chest.isLocked) TypeScript теперь знает, что у сундука есть keyName — string, без undefined. И незапертый сундук с названием ключа тоже ошибка (TS2353).
Программисты называют это «сделать невозможные состояния невозможными»: хороший тип допускает только осмысленные данные. Вот признаки в ваших типах, на которые стоит обратить внимание:
booleanс необязательными свойствами, которые имеют смысл только приtrueили только приfalse,- несколько необязательных свойств, которые всегда либо все на месте, либо все отсутствуют,
status: stringс комментариями о том, какие свойства подходят к какому статусу.
Каждый из них — это объединение, которое ждёт, чтобы его написали. Если состояний больше двух, берите вместо boolean текстовый тег, например status: "fighting" | "fleeing" | "defeated".
Задание
- 1
Программа дважды выводит
undefined, потому что два монстра в списке не могут существовать. Замените типMonsterобъединением из сражающегося монстра,{ name: string; isDefeated: false; health: number }, и побеждённого,{ name: string; isDefeated: true; defeatedBy: string }. TypeScript сообщит о двух монстрах. Исправьте их: тролль ещё сражается, у него40здоровья, а скелета победилБруно. Программа выведет:Волк: здоровье 30 Гоблин: побеждён (Кира) Тролль: здоровье 40 Скелет: побеждён (Бруно) - 2
Под циклом напишите функцию
defeatс параметрамиmonster(Monster) иhero(текст) и возвращаемым типомMonster. Она возвращает нового, побеждённого монстра с тем же именем, которого победилhero. (Без возвращаемого типа TypeScript вывел бы для нового объектаisDefeated: boolean, а это не подходит подMonster.) ВыведитеdescribeMonster(defeat(monsters[0], "Лена")). Программа выведет:Волк: здоровье 30 Гоблин: побеждён (Кира) Тролль: здоровье 40 Скелет: побеждён (Бруно) Волк: побеждён (Лена)