Narrowing TypeScript Unions without the `in` operator
When using a Union Type such as this:
interface BaseAnimal {
weight: number;
}
export interface Bird extends BaseAnimal {
fly: Function;
}
export interface Fish extends BaseAnimal {
fins: number;
swim: Function;
}
export type Animal = Bird | Fish;
TypeScript does not let us write code like this:
let animal: Animal;
if (animal.swim) { // Error!
animal.swim();
}
TypeScript errors out with Property 'swim' does not exist on type 'Bird'.
Instead, we are “forced” to use the in operator:
let animal: Animal;
if ('swim' in animal) { // TypeScript is ok with this
animal.swim();
}
But if we really want to avoid the in operator, for whatever reason, there is a type “trick” we can use:
export interface Bird extends BaseAnimal {
fly: Function;
swim?: undefined // hack
}
We can type that same property as undefined, and now TypeScript no longer complains:
let animal: Animal;
if (animal.swim) { // All good!
animal.swim();
}
It’s a bit of a hack, and I wouldn’t recommend this for general use, but it can be helpful when used judiciously.