Intersections
A boss is a monster with something extra: it fights in phases, and it gives a big reward. In the lesson on object types, you'd write interface Boss extends Monster { … }. With type, the same is written with &:
type Monster = {
name: string;
health: number;
attack: number;
};
type Boss = Monster & { phase: number; reward: number };
const dragon: Boss = { name: "Dragon", health: 300, attack: 25, phase: 1, reward: 500 };
console.log(`${dragon.name}: health ${dragon.health}, phase ${dragon.phase}`);This prints:
Dragon: health 300, phase 1A & B is an intersection type: a value of this type must be an A and a B at the same time, so it has all the properties of A and all the properties of B. The name may sound strange: the intersection of two things is usually less than each of them, not more. But it's the values that are fewer: only the objects that fit both types. And those objects have more properties.
Compare it with a union, which you know from the lesson on unions. A | B is one or the other, so before you narrow it, you can use only what both have. A & B is both at once, so you can use everything.
If a boss is missing something, the message says which part of the intersection wants it:
const troll: Boss = { name: "Troll", 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, rewardA Boss is also a Monster, so it can go wherever a monster is expected, for example into a Monster[], just as with extends.
So which one should you use? For object types, both do the same job. interface … extends is often clearer, and it reports a conflict right where you declare the interface: if Monster had id: number and the boss id: string, you'd get Interface 'Boss' incorrectly extends interface 'Monster'. An intersection doesn't report the conflict, and the property's type becomes never, a type with no values. You only find out when you create an object: Type 'number' is not assignable to type 'never'. On the other hand, & joins any types, for example two types that you already have, type Mercenary = Hero & Merchant;, or a type with a utility type, Omit<Hero, "id"> & { joined: number }.
A typical mistake is | instead of &. With type Boss = Monster | { phase: number };, a boss is only one of the two, and boss.phase gives an error:
main.ts(10,17): error TS2339: Property 'phase' does not exist on type 'Boss'.
Property 'phase' does not exist on type 'Monster'.Instructions
Under Monster, create the type Boss: a Monster with two more properties, phase and reward, both numbers. Use &.
Under the monsters, create a constant dragon of type Boss: the Dragon with 300 health, attack 25, phase 1 and a reward of 500. Then write a function describeBoss(boss: Boss): string and print what it returns for the dragon:
Dragon (health 300, attack 25), phase 1, reward 500 goldUnder it, create a constant monsters of type Monster[] with the goblin, the wolf and the dragon, and print each monster's name and attack in a for...of loop:
Goblin: attack 3
Wolf: attack 5
Dragon: attack 25Start programming for free
6/7