Защитники типов (type guards)
typeof отличает строки от чисел. А что делать с объединением двух типов объектов? Для обоих typeof выдаёт "object":
type Potion = { name: string; heal: number };
type Weapon = { name: string; damage: number };
type Item = Potion | Weapon;
const inventory: Item[] = [
{ name: "Лечебное зелье", heal: 20 },
{ name: "Серебряный меч", damage: 12 },
];У Item можно прочитать name, который есть у обоих типов, но не heal. В for (const item of inventory) строка console.log(item.heal); даёт:
main.ts(11,22): error TS2339: Property 'heal' does not exist on type 'Item'.
Property 'heal' does not exist on type 'Weapon'.В JavaScript есть оператор, который проверяет, есть ли у объекта свойство: "heal" in item равно true, если у item есть свойство heal. TypeScript сужает тип с его помощью. heal есть только у Potion, поэтому внутри if предмет — это Potion, а в ветке else — Weapon:
for (const item of inventory) {
if ("heal" in item) {
console.log(`${item.name}: лечение ${item.heal}`);
} else {
console.log(`${item.name}: урон ${item.damage}`);
}
}Программа выведет:
Лечебное зелье: лечение 20
Серебряный меч: урон 12Когда одна и та же проверка нужна в нескольких местах, хочется вынести её в функцию. Но функция, которая возвращает boolean, ничего не сужает:
function isPotion(item: Item): boolean {
return "heal" in item;
}
for (const item of inventory) {
if (isPotion(item)) {
console.log(`${item.name}: лечение ${item.heal}`);
}
}TypeScript снова сообщает Property 'heal' does not exist on type 'Item'. Из возвращаемого типа boolean он узнаёт лишь, что функция возвращает true или false, но не то, что это говорит о предмете. Для этого пишут особый возвращаемый тип, item is Potion:
function isPotion(item: Item): item is Potion {
return "heal" in item;
}Функция по-прежнему возвращает true или false, но теперь TypeScript знает: когда она вернула true, item — это Potion. Такая функция называется защитником типа (type guard). С ней if (isPotion(item)) сужает тип так же, как проверка in внутри неё, а в ветке else предмет — это Weapon. Имя перед is — это имя параметра.
(Если вообще не указывать возвращаемый тип, современные версии TypeScript иногда могут сами вывести item is Potion. С : boolean вы сказали им, что это просто логическое значение.)
Типичные ошибки:
- TypeScript доверяет защитнику типа: он не проверяет, что в теле проверяется нужное. Если по ошибке написать
return "damage" in item;, TypeScript будет считать мечи зельями, и цикл выше выведетСеребряный меч: лечение undefined. - Опечатку в названии свойства он тоже не замечает.
"heel" in itemдля любого предмета простоfalse, поэтому ни один предмет не окажется зельем.
В следующем уроке вы увидите более распространённый способ различать типы объектов: свойство вроде kind, которое говорит, чем является каждый объект. Там TypeScript ловит такие опечатки.
Задание
- 1
В битве участвуют герои и монстры. Напишите защитник типа
isMonsterс параметромfighterтипаFighter: с помощьюinон проверяет, является ли боецMonster. - 2
Переберите
battleцикломfor...ofи с помощьюisMonsterвыведите каждого бойца: монстра — с его силой, героя — с его уровнем. Тогда программа выведет:Кира (уровень 3) гоблин (сила 4) Бруно (уровень 2) тролль (сила 15) - 3
Сложите силу всех монстров и выведите сумму после цикла:
Кира (уровень 3) гоблин (сила 4) Бруно (уровень 2) тролль (сила 15) Сила монстров: 19