Union Types in TypeScript: A Depth Guide

Updated: January 8, 2024 By: Guest Contributor Post a comment

Overview

Union types in TypeScript allow you to describe the value that may be one of several types. This comprehensive guide delves into using and leveraging union types.

Introduction to Union Types

TypeScript is a statically typed superset of JavaScript, providing optional static typing, classes, and interface. One of the features that TypeScript brings to the table is Union Types. A union type is a way to declare a variable or a function that can hold/return one of several types of values. Here’s the basic syntax to denote a union type:

let value: string | number;

This indicates that the variable value can be either a string or a number.

Getting Started with Union Types

Let’s begin with a simple example:

function combine(input1: string | number, input2: string | number) {
    if (typeof input1 === 'string' || typeof input2 === 'string') {
        return input1.toString() + input2.toString();
    }
    return input1 + input2;
}

In this function, both input1 and input2 can be either a string or a number. Depending on the runtime types of the inputs, the function combines them either by concatenation or by addition.

Advanced Union Types

Advanced applications of union types involve using them with custom types and interfaces:

interface Bird {
    type: 'bird';
    flyingSpeed: number;
}

interface Horse {
    type: 'horse';
    runningSpeed: number;
}

type Animal = Bird | Horse;

function moveAnimal(animal: Animal) {
    let speed;
    switch (animal.type) {
        case 'bird':
            speed = animal.flyingSpeed;
            break;
        case 'horse':
            speed = animal.runningSpeed;
            break;
    }
    console.log('Moving at speed: ' + speed);
}

Here, Animal is a union type that can be either a Bird or a Horse, each with its unique properties to describe its movement.

Type Guards and Differentiating Union Types

Type guards are conditions that help TypeScript infer the correct type within the union. Common ways to differentiate union types are using typeof, instanceof, or custom type guards:

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

// Usage
const pet = getSomePet();

if (isFish(pet)) {
    pet.swim();
} else {
    pet.fly();
}

This technique aids in writing robust code when dealing with complex type unions.

Union Types and Arrays

Union types can also be used with arrays to represent collections that contain multiple types:

let mixedArray: (string | number)[] = ['hello', 10, 'world', 20];

In the array mixedArray, every element can either be a string or a number, which can be extremely useful in certain situations.

Union Types with Generics

TypeScript’s generics can also be combined with union types to create flexible and reusable code structures:

function merge<T extends object, U extends object>(objA: T, objB: U): T & U {
    return Object.assign({}, objA, objB);
}

const mergedObj = merge({ name: 'John' }, { age: 30 });

The merge function accepts two different objects and merges them. Using generics here makes the function more versatile.

Union Types and Type Aliases

Union types work well with type aliases to provide a better type definition and readability:

type StringOrNumber = string | number;

function addWithAlias(arg1: StringOrNumber, arg2: StringOrNumber): StringOrNumber {
    // Implementation would be similar to the combine function shown earlier
}

Type aliases can simplify complex type unions and make the code easier to maintain.

Conclusion

Union types in TypeScript are powerful tools to create flexible, clean, and predictable codebases that take advantage of JavaScript’s dynamic nature while still enforcing type safety. Incorporating unions into your TypeScript development can significantly improve the quality and maintainability of your code.