Union types
Like intersection types, union types allow us to combine the properties of existing primitive and custom types to make composites.
Intersection types work well when you have complete knowledge of what form your data will take. This approach only works if you know in advance that your data will be and that it will share properties from more than one type. But what if you don’t have this knowledge? You only know that it will be one of the three possible types? This is where union types become helpful. Instead of saying that event X will be of type A we say that event X may be one of types A or B or C.
A union type is a type formed from two or more other types, representing values that may be any one of those types. We refer to each of these types as the union’s members.
Demonstration
type Petrol = {
maxSpeed: number;
tankVolume: number; // unique property
};
type Electric = {
maxSpeed: number;
maxCharge: number; // unique property
};
type Car = Petrol | Electric;
const tesla: Car = {
maxSpeed: 120,
maxCharge: 22,
};
- We have defined two custom types:
Petrol
andElectric
- We have unified them to make a new custom type of
Car
tesla
is an instance of the union type
Usage with functions
Union type syntax is not limited to the declaration phase. You can also use them as parameters to functions or function return types:
function generateCar(car: Petrol | Electric): void {
console.log(`The car has a max speed of ${car.maxSpeed}`);
}
Unifying primitive and custom types
We can also use unions with primitive types as well as custom types:
function printId(id: number | string): void {
console.log(`Your id is ${id}`);
}
We can also combine primitive and custom types in the same union:
function printStringOrNumber(): string | Petrol {}
Set theory: understanding errors
In JavaScript we have the primitive types of string , number , boolean etc. In TS, these types are distinct and exclusive: if you declare that x : string you cannot redefine it as boolean or expect Boolean properties and methods to belong to a string type. For instance a string type is incapable of having as its value false.
This is not true in the case of unions.
With a union of string | boolean
a value x
could be 'banana'
or true
. We are allowing for all values that are either from the set string or the set boolean. Logically this is equivalent to the OR operator: a value of type string | boolean
could be one of infinite possible strings or true/false. With union types then, we radically widen the scope of possible values for the type. a
These constraints don’t just apply to the values themselves but the methods and properties you can use with the values. For instance a boolean doesn’t have the property of length
. Therefore if you have a union type of string|boolean
you are going to get an error if you apply length
to it since the property must exist on both types that comprise the union. The way round this is to use type narrowing and execute control flow based on the parameter that is passed to a function that is declared to be of this mixed type.
Here is an example of this:
function printId(id: number | string) {
if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}