DEV Community

Discussion on: Aha! Understanding Typescript's Type Predicates

Collapse
 
karataev profile image
Eugene Karataev

Thanks for the type predicate explanation. But in my case TS (v.3.8.3) is not happy with isCat function:

function isCat(animal: Cat | Dog): animal is Cat {
  return typeof animal.numberOfLives === 'number';
// Property 'numberOfLives' does not exist on type 'Cat | Dog'.
//  Property 'numberOfLives' does not exist on type 'Dog'.(2339)
}
Enter fullscreen mode Exit fullscreen mode

What are other options for isCat implementation? I can think of

function isCat(animal: Cat | Dog): animal is Cat {
    return animal.hasOwnProperty('numberOfLives');
}
Enter fullscreen mode Exit fullscreen mode

This works, but numberOfLives is a plain string. It's easy to make a typo or forget to change this string in the process of refactoring. Is there a better option to create the type predicate?

Collapse
 
karataev profile image
Eugene Karataev

All right, I found that TS has discriminated unions for such cases.
Just extend your interfaces with a common property and use it to narrow types in your code.

interface Cat {
  kind: 'cat';
  numberOfLives: number;
}
interface Dog {
  kind: 'dog';
  isAGoodBoy: boolean;
}

function isCat(animal: Cat | Dog): animal is Cat {
  return animal.kind === 'cat';
}

function getAnimal(): Cat | Dog {
  return {
    kind: 'cat',
    numberOfLives: 7
  }
}

let animal = getAnimal();
animal.numberOfLives // Error. Property 'numberOfLives' does not exist on type 'Cat | Dog'

if (isCat(animal)) {
  animal.numberOfLives // OK
}

Enter fullscreen mode Exit fullscreen mode
Collapse
 
mirkorainer profile image
Mirko Rainer

I use the "as" keyword and then a specific property on the type.

export function isWeapon(item: Item | Weapon | Armor | Shield): item is Weapon {
    return (item as Weapon).damageDice !== undefined;
}
Enter fullscreen mode Exit fullscreen mode

so in your above example you could leave out the "kind" property:

interface Cat {
  numberOfLives: number;
}
interface Dog {
  isAGoodBoy: boolean;
}

function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).numberOfLive !== undefined;
}
Enter fullscreen mode Exit fullscreen mode

I like not having to add the extra property to my types. :)

Full code for my example found in this file: github.com/mirkoRainer/RulesLawyer...