Literal Unions in TypeScript

Literal Types

Literal types in TypeScript allow us to have exact values for strings, numbers, and booleans. They are not all that useful by themselves.

For example:

let dog: 'greyhound' = 'greyhound';
dog = 'husky';

This will have the following type error: Type '"husky"' is not assignable to type '"dog"'.

Unions

Union Types are a powerful feature of the TypeScript type system that allow us to build new types out of existing ones.

For example, here is a function that works string and number:

function addId(id: number | string): number {
    if (typeof id === 'string') {
        return parseFloat(id);
    } else if (typeof id === 'number') {
        return id;
    }
}
// OK:
addId('100');
// Not OK:
addId([100]);

Literal Unions

When literal types are combined with union types, we can create a condition where we only allow certain values, which is quite useful.

String

For example for string:

type Direction = 'up' | 'down';

class Controller {
    move(direction: Direction) {
        if (direction === 'up') {
            ...
        } else if (direction === 'down') {
            ...
        }
    }
}
let controller = new Controller();
Controller.move('up');
Controller.move('left');

The second invocation will result in a type error: Argument of type '"left"' is not assignable to parameter type 'Direction'.

Number

Numbers work similarly:

function compare(a: string, b: string): -1 | 0 | 1 {
    return a === b ? 0 : a > b ? 1 : -1;
}

Boolean

Of course, booleans only have two values - so effectively the boolean type is simply the union true | false:

function contestantAnswer(answer: true | false) {
    return answer;
}

is the same as:

function contestantAnswer(answer: boolean) {
    return answer;
}

What about Enums?

Enums give us another way to express types that only accept certain literal values.

When an enum member’s value changes, we don’t have to change any other code. With a literal union, we might have to update the value in many places.

From Execute Program:

“We recommend avoiding enums for two reasons. First, enums break TypeScript’s core “type-level extension” rule, complicating our mental model of the language. Second, unions can do the same thing as enums, allowing only a certain set of literal types, with no major drawbacks.”

Conclusion

Here we see how a not particularly useful feature (literals) becomes powerful by combining with another feature (unions) in TypeScript.


Erik August Johnson is a software developer working with JavaScript to build useful things. Follow them on Twitter.