🎉 I'm releasing 12 products in 12 months! If you love product, checkout my new blog workingoutloud.dev

Back to home

Advanced Union And Intersection Types In Typescript

This post will is 9 of 20 for my series on intermediate-to-advance TypeScript tips.

Introduction

In TypeScript, one of the powerful features that allow us to create complex types are Union and Intersection types. These types provide us with the ability to create new types from existing ones, leading to more flexible and reusable code. This post will delve into the practical use of these advanced types, with examples that you can try out in the TypeScript Playground. For a more detailed explanation, you can refer to the TypeScript Handbook's section on Unions and Intersection Types.

Union Types

Union types are a way of declaring a type that could be one of several types. We use the vertical bar (|) to separate each type.

Here's an example of a function that uses a union type:

function padLeft(value: string, padding: string | number) { // ... }

In this function, the padding parameter can be either a string or a number. This is a simple yet powerful way to handle multiple types in a single function. You can try this out in the TypeScript Playground.

Intersection Types

Intersection types are another powerful feature in TypeScript. They allow you to combine multiple types into one, resulting in a type that has all the properties of the combined types.

Here's an example of intersection types:

interface ErrorHandling { success: boolean; error?: { message: string }; } interface ArtworksData { artworks: { title: string }[]; } type ArtworksResponse = ArtworksData & ErrorHandling;

Here, ArtworksResponse is an intersection type that combines ArtworksData and ErrorHandling. An object of type ArtworksResponse will have all the properties of ArtworksData and ErrorHandling.

Now, let's create a function that takes an ArtworksResponse object and processes it:

function handleArtworksResponse(response: ArtworksResponse) { if (!response.success) { console.error(response.error?.message); return; } console.log(response.artworks); }

In the handleArtworksResponse function, we first check if the response was successful. If not, we log the error message and return early. If the response was successful, we log the artworks.

Here's an example of how you might call this function:

const response: ArtworksResponse = { success: true, artworks: [{ title: "Mona Lisa" }, { title: "The Starry Night" }], }; handleArtworksResponse(response);

In this example, response is an object that satisfies the ArtworksResponse type, so we can pass it to handleArtworksResponse without any issues. If you run this code in the TypeScript Playground, you should see the array of artworks logged to the console.

This example demonstrates how intersection types can be used to create complex types that ensure our functions are used correctly.

Representing Network State example

Let's dive into a more complex example using both union and intersection types.

Consider a network request scenario where we have different states for our request - it could be in a loading state, a success state, or a failed state. Each of these states has different properties associated with them. Here's how we might model this:

type NetworkLoadingState = { state: "loading"; }; type NetworkFailedState = { state: "failed"; code: number; }; type NetworkSuccessState = { state: "success"; response: { title: string; duration: number; summary: string; }; }; type NetworkState = | NetworkLoadingState | NetworkFailedState | NetworkSuccessState;

In this example, NetworkState is a union of NetworkLoadingState, NetworkFailedState, and NetworkSuccessState. Each of these types has a state property that can be used to determine the current state of the network request.

Now, let's create a function that takes a NetworkState object and processes it:

function networkStatus(state: NetworkState): string { switch (state.state) { case "loading": return "Downloading..."; case "failed": return `Error ${state.code} downloading`; case "success": return `Downloaded ${state.response.title} - ${state.response.summary}`; } }

In the networkStatus function, we use a switch statement to handle each possible state. The TypeScript compiler can infer the type of state in each case clause. This is known as "discriminant union types", a pattern where TypeScript can narrow down the type based on a certain property (in this case, state).

This example demonstrates how union and intersection types can be used together to create complex and robust type definitions. You can try this out in the TypeScript Playground.

Summary

Union and Intersection types are powerful tools in TypeScript that allow us to create complex types from existing ones. Union types let us declare a type that could be one of several types, while Intersection types allow us to combine multiple types into one. These features make our code more flexible and reusable, and they are essential for writing complex TypeScript applications.

References

  1. TypeScript Handbook: Unions and Intersection Types

Photo credit: fynngeerdsen

Personal image

Dennis O'Keeffe

@dennisokeeffe92
  • Melbourne, Australia

Hi, I am a professional Software Engineer. Formerly of Culture Amp, UsabilityHub, Present Company and NightGuru.
I am currently working on Visibuild.

1,200+ PEOPLE ALREADY JOINED ❤️️

Get fresh posts + news direct to your inbox.

No spam. We only send you relevant content.

Advanced Union And Intersection Types In Typescript

Introduction

Share this post