Welcome to The Coding College, where we simplify programming concepts to help you become a better developer! At The Coding College, we are committed to delivering practical programming knowledge. Today, we’re diving into TypeScript Union Types—a powerful feature that enhances type safety and flexibility.
What Are Union Types in TypeScript?
A Union Type in TypeScript allows a variable to hold multiple types of values. Instead of restricting a variable to a single type, union types enable flexibility while maintaining type safety.
Syntax
type TypeA = Type1 | Type2 | Type3;
The pipe symbol (|
) separates the types that can be assigned to the variable.
Why Use Union Types?
Union types are useful when:
- A variable can represent different types of data (e.g., a value that can be
string
ornumber
). - Function parameters or return values have multiple possible types.
- You want to enforce type safety without overcomplicating the code.
Examples of Union Types
1. Basic Union Types
let value: string | number;
value = "Hello"; // Valid
value = 42; // Valid
// value = true; // Error: Type 'boolean' is not assignable to 'string | number'
2. Union Types in Function Parameters
function printId(id: string | number) {
console.log(`Your ID is: ${id}`);
}
printId("ABC123"); // Output: Your ID is: ABC123
printId(12345); // Output: Your ID is: 12345
3. Union Types in Function Return Values
function getStatus(flag: boolean): string | number {
return flag ? "Success" : 0;
}
console.log(getStatus(true)); // Output: Success
console.log(getStatus(false)); // Output: 0
Type Narrowing
TypeScript provides mechanisms to work with union types more effectively by narrowing them down to specific types.
1. Using typeof
The typeof
operator helps determine the type of a variable at runtime.
function display(value: string | number) {
if (typeof value === "string") {
console.log(`String value: ${value}`);
} else {
console.log(`Number value: ${value}`);
}
}
display("Hello"); // Output: String value: Hello
display(42); // Output: Number value: 42
2. Using instanceof
When working with classes or objects, instanceof
can be used to narrow types.
class Dog {
bark() {
console.log("Woof!");
}
}
class Cat {
meow() {
console.log("Meow!");
}
}
function handleAnimal(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}
handleAnimal(new Dog()); // Output: Woof!
handleAnimal(new Cat()); // Output: Meow!
3. Using Custom Type Guards
Custom type guards allow you to define specific checks for narrowing.
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function isFish(animal: Fish | Bird): animal is Fish {
return (animal as Fish).swim !== undefined;
}
function handleAnimal(animal: Fish | Bird) {
if (isFish(animal)) {
animal.swim();
} else {
animal.fly();
}
}
Union Types with Arrays
Union types can also apply to arrays.
Example: Array of Multiple Types
let mixedArray: (string | number)[] = ["Alice", 42, "Bob"];
console.log(mixedArray); // Output: ["Alice", 42, "Bob"]
Example: Mixed Arrays
type User = { name: string };
type Admin = { name: string; privileges: string[] };
let people: (User | Admin)[] = [
{ name: "John" },
{ name: "Jane", privileges: ["Admin"] },
];
Union Types vs Intersection Types
| Feature | Union Types (|
) | Intersection Types (&
) |
|——————————|——————————————|——————————————|
| Definition | Combines types where a value can be one or the other | Combines types where a value must satisfy all types |
| Use Case | For multiple possibilities (e.g., string or number) | For combining features from multiple types |
| Example | string | number
| { name: string } & { age: number }
|
Real-World Applications
1. Flexible Function Inputs
Union types simplify APIs that accept multiple types.
function processInput(input: string | HTMLElement) {
if (typeof input === "string") {
console.log(`Input string: ${input}`);
} else {
console.log(`Input element ID: ${input.id}`);
}
}
2. Handling Diverse Data Structures
Union types are ideal when dealing with varying data structures.
type ApiResponse = { success: true; data: any } | { success: false; error: string };
function handleResponse(response: ApiResponse) {
if (response.success) {
console.log("Data:", response.data);
} else {
console.error("Error:", response.error);
}
}
Common Pitfalls with Union Types
1. Overlapping Properties
When types in a union share properties, TypeScript may need explicit type narrowing.
type Square = { size: number };
type Rectangle = { width: number; height: number };
function area(shape: Square | Rectangle) {
// Error: Property 'width' does not exist on type 'Square'.
if (shape.width) {
return shape.width * shape.height;
}
}
Solution: Use Type Guards
function area(shape: Square | Rectangle) {
if ("width" in shape) {
return shape.width * shape.height;
} else {
return shape.size * shape.size;
}
}
2. Overuse of Union Types
Using union types excessively can make code complex and hard to maintain.
Best Practices
- Narrow Types Early: Use type guards to simplify working with union types.
- Keep Union Types Simple: Avoid overly complex unions that mix many unrelated types.
- Document Usage: Clearly document what each type in the union represents.
Conclusion
Union types are a versatile feature in TypeScript that combine flexibility with type safety. By understanding and effectively narrowing types, you can write more robust and readable code.