Simplifying TypeScript code with Union Types
Let’s imagine we have the following TypeScript interfaces:
export interface Animal {
weight: number;
}
export interface Bird extends Animal {
wings: number;
fly: Function;
}
export interface Fish extends Animal {
fins: number;
swim: Function;
}
Our code deals with all kinds of animals so all our functions look more or less like so:
function observe(animal: Animal) {
// ...
}
There’s an issue though. What if we need to know specifically whether we have a Bird
? While checking for animal.wings
is perfectly valid JavaScript, TypeScript errors out with Property 'wings' does not exist on type 'Animal'
. Which makes sense: the Animal
interface only declares a weight
property.
The solution, then, is to use what’s called a Union Type:
function observe(animal: Bird | Fish) {
// ...
}
A union type describes a value that can be one of several types. Here animal can either be a Bird
or a Fish
. And since a Bird
has wings
TypeScript won’t complain if we make a check for that property.
However, this has some drawbacks:
- The type declaration is wider, affecting the shape of the code.
- Adding a new animal classification like
Reptile
will potentially require changing a lot of type declarations.
What should we do then?
Well, we can hide (not export) the base interface and export Animal
as a union of all animal classification types:
interface BaseAnimal {
weight: number;
}
export interface Bird extends BaseAnimal {
wings: number;
fly: Function;
}
export interface Fish extends BaseAnimal {
fins: number;
swim: Function;
}
export type Animal = Bird | Fish;
Now to add Reptile
we would only need to update the Animal
export.
As an added benefit, this technique allows TypeScript to better infer types.
Whereas before we had to do this:
let animal: BaseAnimal;
if ('wings' in animal) {
(animal as Bird).fly(); // TypeScript will throw an error unless animal is cast to Bird.
}
Now we can do this:
let animal: Animal;
if ('wings' in animal) {
animal.fly(); // TypeScript already knows animal is a Bird!
}