Пересечения
Босс — это монстр с чем-то особенным: он сражается в несколько фаз и даёт большую награду. В уроке про типы объектов вы бы написали interface Boss extends Monster { … }. С type то же самое записывается с помощью &:
type Monster = {
name: string;
health: number;
attack: number;
};
type Boss = Monster & { phase: number; reward: number };
const dragon: Boss = { name: "Дракон", health: 300, attack: 25, phase: 1, reward: 500 };
console.log(`${dragon.name}: здоровье ${dragon.health}, фаза ${dragon.phase}`);Программа выводит:
Дракон: здоровье 300, фаза 1A & B — это тип-пересечение (intersection): значение такого типа должно быть и A, и B одновременно, поэтому у него есть все свойства A и все свойства B. Название может показаться странным: пересечение двух вещей обычно меньше каждой из них, а не больше. Но меньше именно значений: подходят только объекты, которые соответствуют обоим типам. А у таких объектов свойств больше.
Сравните с объединением, которое вы знаете из урока про объединения. A | B — это одно или другое, поэтому, пока вы не сузили тип, можно использовать только то, что есть у обоих. A & B — оба сразу, поэтому можно использовать всё.
Если боссу чего-то не хватает, сообщение называет ту часть пересечения, которая этого требует:
const troll: Boss = { name: "Тролль", health: 80, attack: 9 };main.ts(10,7): error TS2322: Type '{ name: string; health: number; attack: number; }' is not assignable to type 'Boss'.
Type '{ name: string; health: number; attack: number; }' is missing the following properties from type '{ phase: number; reward: number; }': phase, rewardBoss — это ещё и Monster, поэтому он подходит везде, где ожидается монстр, например в Monster[], как и в случае с extends.
Так что же выбрать? Для объектных типов оба способа делают одно и то же. interface … extends часто понятнее, и он сообщает о конфликте прямо там, где вы объявляете интерфейс: если бы у Monster было id: number, а у босса id: string, вы получили бы Interface 'Boss' incorrectly extends interface 'Monster'. Пересечение о конфликте не сообщает, а тип свойства становится never — типом, у которого нет значений. Вы узнаете об этом, только когда создадите объект: Type 'number' is not assignable to type 'never'. Зато & соединяет любые типы, например два уже имеющихся, type Mercenary = Hero & Merchant;, или тип со вспомогательным типом, Omit<Hero, "id"> & { joined: number }.
Типичная ошибка — | вместо &. При type Boss = Monster | { phase: number }; босс — только одно из двух, и boss.phase вызывает ошибку:
main.ts(10,17): error TS2339: Property 'phase' does not exist on type 'Boss'.
Property 'phase' does not exist on type 'Monster'.Задание
- 1
Под
Monsterсоздайте типBoss: этоMonsterс ещё двумя свойствами,phaseиreward, оба — числа. Используйте&. - 2
Под монстрами создайте константу
dragonтипаBoss: Дракон со здоровьем 300, атакой 25, фазой 1 и наградой 500. Затем напишите функциюdescribeBoss(boss: Boss): stringи выведите то, что она возвращает для дракона:Дракон (здоровье 300, атака 25), фаза 1, награда в золоте: 500 - 3
Под ним создайте константу
monstersтипаMonster[]с гоблином, волком и драконом и выведите имя и атаку каждого монстра в циклеfor...of:Гоблин: атака 3 Волк: атака 5 Дракон: атака 25