Imagine you have different interfaces, precisely ones that extends a general one. Let’s take those interface as examples :
interface Pet { name: string specie: 'fish' | 'bird' } interface Fish extends Pet { swim(): any } interface Bird extends Pet { fly(): any }
Now you are in a case when you know from your code that the Pet
you will use will be a Fish
, Typescript will throw you an error if you want to use a function from a Fish
:
const callPet = (pet: Pet) => { console.log(`${pet.name} come !`) } const callPetToCome = (pet: Pet) => { callPet(pet) if (pet.specie === 'fish') { pet.swim() } if (pet.specie === 'bird') { pet.fly() } }
Typescript will throw you an error that
pet.swim()
orpet.fly()
because we did not defined it :js.ts(14,42): error TS2339: Property ‘swim/fly’ does not exist on type Pet.
The problem here is that we can have different type on one Pet. We need to use user-defined type guard to allow the previous Typescript type Pet
to be Fish
or Bird
so that Typescript will know the new interface it deals with.
To do that we add those user-defined type guard that will return a type predicate and implement them in our if statement:
const isFish = (pet: Pet): pet is Fish => { return pet.specie === 'fish' } const isBird = (pet: Pet): pet is Fish => { return pet.specie === 'bird' } const callPetToCome = (pet: Pet) => { callPet(pet) if (isFish(pet)) { pet.swim() } if (isBird(pet)) { pet.fly() } }
Now Typescript will know which interface is used and avoid any error. With this implementation you have a strict type interface with more granularity than just the global Pet
.
Always keep update your interfaces (this methods can be useful if you have different users in your product with each different methods) and you will avoid errors thanks to Typescript !