Understanding the Difference between `&` and `|` in TypeScript

TypeScript, a superset of JavaScript, brings static typing to the language, enhancing code reliability and maintainability. Two important operators in TypeScript for working with types are the intersection (&) and union (|) operators. These operators allow developers to create complex types by combining simpler ones. In this blog post, we’ll explore the fundamental concepts, usage methods, common practices, and best practices related to the & and | operators in TypeScript.

Table of Contents

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

Fundamental Concepts

Intersection Types (&)

The intersection type operator (&) combines multiple types into one. A variable of an intersection type must satisfy all the types in the intersection. In other words, it has all the properties and methods of each individual type.

type Admin = {
    role: 'admin';
    manageUsers: () => void;
};

type Employee = {
    employeeId: number;
    work: () => void;
};

type AdminEmployee = Admin & Employee;

const adminEmployee: AdminEmployee = {
    role: 'admin',
    employeeId: 123,
    manageUsers: () => console.log('Managing users...'),
    work: () => console.log('Working...')
};

Union Types (|)

The union type operator (|) allows a variable to have one of several types. A variable of a union type can be of any of the types in the union.

type StringOrNumber = string | number;

const value1: StringOrNumber = 'hello';
const value2: StringOrNumber = 123;

Usage Methods

Using Intersection Types

Intersection types are useful when you want to combine the features of multiple types. For example, you can use them to create a type that has the properties of two or more existing types.

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

type ContactInfo = {
    email: string;
    phone: string;
};

type PersonWithContactInfo = Person & ContactInfo;

const person: PersonWithContactInfo = {
    name: 'John Doe',
    age: 30,
    email: '[email protected]',
    phone: '123-456-7890'
};

Using Union Types

Union types are handy when a variable can have different types depending on the situation. For instance, a function parameter might accept either a string or a number.

function printValue(value: string | number) {
    if (typeof value === 'string') {
        console.log(`The string value is: ${value}`);
    } else {
        console.log(`The number value is: ${value}`);
    }
}

printValue('hello');
printValue(123);

Common Practices

Intersection for Mixins

Intersection types can be used to implement mixins, which are a way to combine multiple classes into one.

class Logger {
    log(message: string) {
        console.log(`[LOG] ${message}`);
    }
}

class Validator {
    validate(value: any) {
        return typeof value === 'string';
    }
}

type LoggerValidator = Logger & Validator;

const loggerValidator: LoggerValidator = {
    ...new Logger(),
    ...new Validator()
};

loggerValidator.log('Validating value...');
const isValid = loggerValidator.validate('hello');
console.log(`Is valid: ${isValid}`);

Union for Optional Parameters

Union types are often used for optional parameters in functions. You can use them to specify that a parameter can either have a certain type or be undefined.

function greet(name: string | undefined) {
    if (name) {
        console.log(`Hello, ${name}!`);
    } else {
        console.log('Hello!');
    }
}

greet('John');
greet(undefined);

Best Practices

Avoiding Overly Complex Types

While intersection and union types can be powerful, it’s important to avoid creating overly complex types. Complex types can make your code hard to understand and maintain. If a type becomes too complicated, consider breaking it down into smaller, more manageable types.

Using Type Guards with Unions

When working with union types, it’s often necessary to use type guards to determine the actual type of a variable. Type guards are expressions that perform a runtime check that guarantees the type in a certain scope.

function printLength(value: string | number[]) {
    if (typeof value === 'string') {
        console.log(`The length of the string is: ${value.length}`);
    } else {
        console.log(`The length of the array is: ${value.length}`);
    }
}

printLength('hello');
printLength([1, 2, 3]);

Conclusion

The & and | operators in TypeScript are powerful tools for working with types. Intersection types allow you to combine multiple types into one, while union types allow a variable to have one of several types. By understanding the fundamental concepts, usage methods, common practices, and best practices related to these operators, you can write more robust and flexible TypeScript code.

References