Mastering Union Types and Intersection Types in TypeScript
Introduction
TypeScript, developed by Microsoft, is a typed superset of JavaScript that compiles to plain JavaScript. One of the key features of TypeScript is its type system, which makes it easier to catch bugs and enhance the development experience. Among the mechanisms in TypeScript’s type system, Union Types and Intersection Types stand out as powerful tools for creating flexible and robust code. This article will delve into what Union Types and Intersection Types are, how to use them, and provide examples to help beginners master these concepts.
Union Types
Union Types allow a variable to hold more than one type. It’s like saying, “This variable can be of type X or type Y.” This makes your code more flexible and enables you to define variables that can work with multiple types of values.
Syntax of Union Types
In TypeScript, you can define a Union Type using the pipe (|
) symbol between the types. Here’s an example:
let value: string | number;
value = "Hello"; // Valid
value = 10; // Valid
value = true; // Invalid
In this example, the value
variable can hold either a string or a number, but not a boolean.
Practical Use Cases for Union Types
Union Types can be particularly useful in scenarios where a function can accept multiple types of parameters or return different types of results based on its logic.
Example: Function with Union Type Parameters
function printId(id: string | number) {
console.log(`Your ID is: ${id}`);
}
printId("12345"); // Output: Your ID is: 12345
printId(67890); // Output: Your ID is: 67890
Example: Function Returning Union Type
function getValue(flag: boolean): string | number {
if (flag) {
return "Flag is true";
} else {
return 0;
}
}
In both examples, the functions can handle multiple types, making the code more versatile.
Type Guards with Union Types
When working with Union Types, it’s often necessary to determine the actual type of a variable at runtime. TypeScript provides type guards to help with this.
Using typeof
for Primitive Types
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(123); // Output: Number value: 123
Using instanceof
for Object Types
class Car {
drive() {
console.log("Driving a car...");
}
}
class Bike {
ride() {
console.log("Riding a bike...");
}
}
function move(vehicle: Car | Bike) {
if (vehicle instanceof Car) {
vehicle.drive();
} else {
vehicle.ride();
}
}
let myCar = new Car();
let myBike = new Bike();
move(myCar); // Output: Driving a car...
move(myBike); // Output: Riding a bike...
Intersection Types
Intersection Types allow you to combine multiple types into one. It’s like saying, “This variable must be of all these types at once.” This makes your code more expressive, as you can aggregate multiple type definitions into a single entity that carries all their properties.
Syntax of Intersection Types
In TypeScript, you can define an Intersection Type using the ampersand (&
) symbol between the types. Here’s an example:
type Person = {
name: string;
age: number;
};
type Employee = {
employeeId: number;
position: string;
};
type EmployeeDetails = Person & Employee;
let employee: EmployeeDetails = {
name: "John Doe",
age: 30,
employeeId: 12345,
position: "Software Engineer"
};
In this example, the employee
variable must satisfy both the Person
and Employee
types.
Practical Use Cases for Intersection Types
Intersection Types can be particularly useful in scenarios where you want to compose different types to create a new type with combined properties and methods.
Example: Combining Multiple Interfaces
interface Drivable {
drive(): void;
}
interface Flyable {
fly(): void;
}
type FlyingCar = Drivable & Flyable;
class FutureCar implements FlyingCar {
drive() {
console.log("Driving a future car...");
}
fly() {
console.log("Flying a future car...");
}
}
let myFutureCar = new FutureCar();
myFutureCar.drive(); // Output: Driving a future car...
myFutureCar.fly(); // Output: Flying a future car...
Example: Detailed Object Descriptions
Intersection Types can help you create more detailed object descriptions by merging various type definitions.
type Address = {
street: string;
city: string;
};
type ContactInfo = {
email: string;
phone: string;
};
type UserProfile = Address & ContactInfo & {
name: string;
};
let user: UserProfile = {
name: "Alice",
street: "123 Main St",
city: "Wonderland",
email: "alice@example.com",
phone: "123-456-7890"
};
Combining Union Types and Intersection Types
TypeScript allows you to combine Union Types and Intersection Types to create even more complex type definitions. This can provide a balance between flexibility and strictness.
Example: Complex Type Definitions
type Admin = {
adminId: number;
privileges: string[];
};
type User = {
userId: number;
username: string;
};
type AdminUser = Admin & User;
type PersonType = string | number | boolean;
function describePerson(person: PersonType | AdminUser) {
if (typeof person === "object" && 'adminId' in person) {
console.log(`Admin User: ${person.username}, Privileges: ${person.privileges.join(", ")}`);
} else {
console.log(`Person: ${person}`);
}
}
let admin: AdminUser = {
adminId: 101,
privileges: ["manage-users", "edit-settings"],
userId: 1,
username: "adminUser"
};
describePerson(admin); // Output: Admin User: adminUser, Privileges: manage-users, edit-settings
describePerson("Charlie"); // Output: Person: Charlie
Conclusion
Union Types and Intersection Types in TypeScript provide powerful tools for creating flexible yet robust type definitions. They enhance the ability to write code that is both versatile and type-safe, which is a significant advantage when working on large projects or complex applications. Understanding and mastering these concepts will undoubtedly elevate your TypeScript skills and allow you to write more efficient and maintainable code.
For further reading and to deepen your knowledge, consider visiting the Wikipedia article on TypeScript.