Type Guards

typeof tells texts from numbers. But what about a union of two object types? For both of them, typeof gives "object":

type Potion = { name: string; heal: number };
type Weapon = { name: string; damage: number };
type Item = Potion | Weapon;

const inventory: Item[] = [
    { name: "Healing Potion", heal: 20 },
    { name: "Silver Sword", damage: 12 },
];

On an Item, you may read name, which both types have, but not heal. In for (const item of inventory), the line console.log(item.heal); gives:

main.ts(11,22): error TS2339: Property 'heal' does not exist on type 'Item'.
  Property 'heal' does not exist on type 'Weapon'.

JavaScript has an operator that checks whether an object has a property: "heal" in item is true when item has a property called heal. TypeScript narrows with it. Only a Potion has heal, so inside the if, the item is a Potion, and in the else branch, a Weapon:

for (const item of inventory) {
    if ("heal" in item) {
        console.log(`${item.name} heals ${item.heal}`);
    } else {
        console.log(`${item.name} deals ${item.damage} damage`);
    }
}

This prints:

Healing Potion heals 20
Silver Sword deals 12 damage

When you need the same check in several places, you'll want to put it into a function. But a function that returns boolean doesn't narrow anything:

function isPotion(item: Item): boolean {
    return "heal" in item;
}

for (const item of inventory) {
    if (isPotion(item)) {
        console.log(`${item.name} heals ${item.heal}`);
    }
}

TypeScript reports Property 'heal' does not exist on type 'Item'. again. From the return type boolean, it learns only that the function returns true or false, not what that says about the item. For that, you write a special return type, item is Potion:

function isPotion(item: Item): item is Potion {
    return "heal" in item;
}

The function still returns true or false, but now TypeScript knows: when it returns true, item is a Potion. Such a function is called a type guard. With it, if (isPotion(item)) narrows just like the in check inside it, and in the else branch, the item is a Weapon. The name in front of is is the name of the parameter.

(If you leave the return type out completely, current versions of TypeScript can sometimes work out item is Potion themselves. With : boolean, you've told them it's just a boolean.)

Typical mistakes:

  • TypeScript trusts a type guard: it doesn't check that the body tests the right thing. With return "damage" in item; by mistake, TypeScript treats swords as potions, and the loop above prints Silver Sword heals undefined.
  • A typo in the property name isn't reported either. "heel" in item is simply false for every item, so no item is ever a potion.

In the next lesson, you'll see the more common way to tell object types apart: a property such as kind that says what each object is. There, TypeScript catches typos like these.

Instructions

In the battle, there are heroes and monsters. Write a type guard isMonster with a parameter fighter of the type Fighter: it uses in to check whether the fighter is a Monster.

Go through battle with for...of, and use isMonster to print each fighter: a monster with its strength, a hero with their level. The program then prints:

Kira (level 3)
goblin (strength 4)
Bruno (level 2)
troll (strength 15)

Add up the strength of all the monsters, and print it after the loop:

Kira (level 3)
goblin (strength 4)
Bruno (level 2)
troll (strength 15)
Monster strength: 19

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

9/10