TypeScript Union Types
Union types are a powerful feature in TypeScript that allow a variable to be one of several types. We use the pipe symbol (|) to separate each type.
What are Union Types?
When you want a variable, function parameter, or object property to accept values of different types, union types are very useful.
Basic Example
let value: string | number;
value = "Hello, TypeScript"; // OK
value = 123; // OK
// value = true; // Error: Type 'boolean' is not assignable to type 'string | number'.In this example, the value variable can be either a string or a number.
Using Union Types
When a value's type is a union type, we can only access members that are common to all types in the union.
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function getSmallPet(): Fish | Bird {
// ...return a Fish or Bird instance
return { fly: () => {}, layEggs: () => {} };
}
let pet = getSmallPet();
// We can call layEggs() because it's a common method of Fish and Bird
pet.layEggs();
// But we can't directly call fly() or swim() because TypeScript isn't sure which type pet is
// pet.fly(); // Error: Property 'fly' does not exist on type 'Fish | Bird'.Type Narrowing
To use methods or properties specific to a particular type, we need to tell TypeScript the current variable's specific type. This process is called "type narrowing" or "type guards."
There are multiple ways to achieve type narrowing:
1. typeof Type Guard
For primitive types, typeof is the most common type guard.
function printId(id: number | string) {
if (typeof id === "string") {
// In this code block, TypeScript knows id is string type
console.log(id.toUpperCase());
} else {
// Here, id must be number type
console.log(id);
}
}
printId("abc"); // ABC
printId(123); // 1232. instanceof Type Guard
instanceof is used to check if an instance belongs to a certain class.
class Dog { bark() { console.log("Woof!"); } }
class Cat { meow() { console.log("Meow!"); } }
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
// TypeScript knows animal is Dog type
animal.bark();
} else {
// TypeScript knows animal is Cat type
animal.meow();
}
}3. in Operator Type Guard
The in operator can check if an object has a certain property.
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
function move(animal: Bird | Fish) {
if ("fly" in animal) {
// TypeScript knows animal is Bird type
return animal.fly();
}
// TypeScript knows animal is Fish type
return animal.swim();
}Literal Union Types
Union types can be used not only for types but also for specific literal values. This can be used to restrict a variable to only a few specific string or number values.
type Alignment = "left" | "right" | "center";
let textAlign: Alignment;
textAlign = "center"; // OK
textAlign = "left"; // OK
// textAlign = "justify"; // Error: Type '"justify"' is not assignable to type 'Alignment'.
function setMargin(margin: 0 | 10 | 20) {
// ...
}
setMargin(10); // OK
// setMargin(5); // Error: Argument of type '5' is not assignable to parameter of type '0 | 10 | 20'.This pattern is very useful in API design, providing functionality similar to enums but more lightweight.