TypeScript Union and Intersection Types Explained

TypeScript is a statically typed superset of JavaScript that brings a wide range of type - related features to the table. Among these, union and intersection types are powerful tools that allow developers to create more flexible and precise types. Union types enable a variable to hold values of multiple different types, while intersection types combine multiple types into one, requiring a value to satisfy all the specified types. In this blog post, we will explore the fundamental concepts, usage methods, common practices, and best practices of TypeScript union and intersection types.

Table of Contents

  1. Fundamental Concepts
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts

Union Types

A union type in TypeScript is a way to specify that a variable can have one of several types. It is denoted using the | symbol. For example, if you have a variable that can be either a string or a number, you can define its type as string | number.

let value: string | number;
value = "hello"; // valid
value = 123; // valid
// value = true; // invalid

Intersection Types

An intersection type combines multiple types into one. A variable of an intersection type must satisfy all the types involved. It is denoted using the & symbol. Suppose you have two types TypeA and TypeB, and you create an intersection type TypeA & TypeB. A value of this intersection type will have all the properties and methods of both TypeA and TypeB.

type TypeA = {
    propA: string;
};

type TypeB = {
    propB: number;
};

type CombinedType = TypeA & TypeB;

let obj: CombinedType = {
    propA: "test",
    propB: 10
};

Usage Methods

Using Union Types

Union types are useful when a function can accept different types of input. For example, a function that can format either a number or a string.

function formatValue(value: string | number) {
    if (typeof value === 'string') {
        return value.toUpperCase();
    } else {
        return value.toFixed(2);
    }
}

console.log(formatValue("hello")); // HELLO
console.log(formatValue(3.14159)); // 3.14

Using Intersection Types

Intersection types are often used when you want to create a new type that has the features of multiple existing types. Consider two types representing different aspects of a user, and you want to create a new type that combines them.

type UserInfo = {
    name: string;
    age: number;
};

type UserPermissions = {
    canEdit: boolean;
    canDelete: boolean;
};

type FullUser = UserInfo & UserPermissions;

let user: FullUser = {
    name: "John",
    age: 30,
    canEdit: true,
    canDelete: false
};

Common Practices

Union for Optional Values

Unions can be used to represent optional values. For instance, a function parameter can be either a specific type or undefined.

function printMessage(message: string | undefined) {
    if (message) {
        console.log(message);
    } else {
        console.log("No message provided");
    }
}

printMessage("Hello!");
printMessage(undefined);

Intersection for Mixins

Intersection types are great for creating mixins. A mixin is a class or object that can be combined with other classes or objects to add additional functionality.

type Loggable = {
    log: () => void;
};

type Serializable = {
    serialize: () => string;
};

type LoggableAndSerializable = Loggable & Serializable;

class Logger implements Loggable {
    log() {
        console.log("Logging...");
    }
}

class Serializer implements Serializable {
    serialize() {
        return "Serialized data";
    }
}

class MyClass implements LoggableAndSerializable {
    log() {
        console.log("Custom logging");
    }
    serialize() {
        return "Custom serialized data";
    }
}

Best Practices

Type Guards for Unions

When working with union types, it’s important to use type guards to narrow down the type within a conditional block. TypeScript will then have more information about the actual type of the variable.

function getLength(value: string | number[]) {
    if (typeof value === 'string') {
        return value.length; // TypeScript knows value is a string here
    } else {
        return value.length; // TypeScript knows value is a number[] here
    }
}

Avoid Over - Complex Intersections

Intersection types can quickly become complex and hard to manage if you combine too many types. Try to keep your intersections simple and focused on a specific purpose. If an intersection becomes too large, it might be a sign that you need to refactor your types.

Conclusion

TypeScript union and intersection types are powerful features that enhance the type - safety and flexibility of your code. Union types allow variables to hold values of multiple types, which is useful for handling different input scenarios. Intersection types enable you to combine multiple types, creating new types that have the features of all the involved types. By understanding the fundamental concepts, usage methods, common practices, and best practices, you can make the most of these features in your TypeScript projects.

References