This article will explain union types in TypeScript using real-world examples and show you exactly how they’re used. We’ll even include TypeScript playground links for our examples. This way, you can quickly try them yourself. By the end, you’ll know how to use union types like the pros.
What are Union Types?
Sometimes requirements change in a project, and you need to modify your program to be more flexible. At the code level, this could be a function that now accepts a string or a number for an ID value.
You could write two separate functions:
function printStringId(id: string) {
console.log("Your ID is: " + id);
}
function printNumberId(id: number) {
console.log("Your ID is: " + id);
}
That seems kind of silly, though, doesn’t it?
This is where union types come to the rescue! They allow you to define a type using two or more types. Let’s do some refactoring:
function printId(id: string | number) {
console.log("Your ID is: " + id);
}
As you can see, this is a lot simpler! And the flexibility in the parameter type annotation — id: string | number — allows our program to match our requirements! Woohoo!
This is the beauty of union types. And all you have to do is use the | symbol to denote a union. In our example, our union has two members: string and number, meaning these are valid function calls:
// OK
printId(101);
// OK
printId("202");
While this would be an invalid call:
// Error
printId({ myID: 22342 });
As you can see, union types allow you to combine types to match your program. This is important because it allows you to do more with less code. And less code means less to maintain for you and your team. Later, we’ll dive into some examples from open source to see how they’re used in the real world.
Show me the code
If you want to jump straight into code examples, take a look at these TypeScript playground links:
- basic union type examples - TypeScript playground link
- open source examples - TypeScript playground link
Examples of Union Types in Open Source
Now that we’ve discussed the basics of union types let’s dive into some real-world examples from open source. The first one we’ll look at is from Prettier, the most popular code formatting tool in the JavaScript ecosystem. Specifically, we’ll look at an example from the codebase for Prettier’s VS Code extension:
export type PackageManagers = "npm" | "yarn" | "pnpm";
This union type combines three string literals into a single type called PackageManagers. As you can see, union types can span more than two types.
Union Types in React Types
The next example we’ll look at is from the React v17 types:
type ReactText = string | number;
// @ts-expect-error - ReactElement type definition omitted for simplicity
type ReactChild = ReactElement | ReactText;
We have two type definitions here: ReactText and ReactChild. Both utilize union types. In the first, ReactText can be either a string or a number. This means these are valid ReactText elements:
const ExampleStringReactText: ReactText = "Hello, world!"
const ExampleNumberReactText: ReactChild = 21
The second example creates a new type called ReactChild, which is either a ReactElement or ReactText. I like this example because it shows union types built using primitive types (aka string, number) and using custom types, also called type aliases.
The React types live in a central repository called DefinitelyTyped/DefinitelyTyped. This repository contains many of the common types in the TypeScript ecosystem. Every time you run, yarn add -D @types/<package>, you are installing types from this repository. The alternative is to maintain the types in a library’s repo like how Prettier does. I believe the latter is the more common pattern these days.
As you can see, union types are very common in TypeScript. I didn’t spend much time finding these two examples. That’s because they’re used all over in the real world! Knowing how to use them to build more complex types will help you write better production-ready TypeScript. Maybe it’ll even help you write your own open-source library!
Resources
For more in-depth info on union types, take a look at these resources:
Summary
We went from not knowing what union types are to breaking down real examples from large open source projects like Prettier and React. As you saw, union types are everywhere. Shipping production-ready TypeScript code means you need the fundamentals down. Now that you know how to use union types, you can combine types which means less code to do more. More time to ship and less time maintaining bad code. Woohoo! Happy coding!