Discriminated Unions in TypeScript - Conditional Types
Discriminated Unions are a TypeScript feature that facilitates expressing a value that could be one out of multiple possible types.
These types have a common property (a literal type), the 'discriminant', which is used to tell the types apart.
I promise this will make more sense when you see an example.
Let's imagine we're building a chat application. In our chat app, there exist various types of messages. Every type of message has some common properties and some properties that are unique to its type.
A common pattern I would see for something like this would be:
type Message = { content: string; type: "text" | "image" | "video"; caption?: string; duration?: number; }
But this is not ideal, as we only need the duration
for our "video"
type and caption
for our "image"
type. And with this current typing, we can't enforce the conditional typings.
So, how do we enforce it?
This is a perfect scenario for using a Discriminated Union.
Let's start by defining our types:
type Message = TextMessage | ImageMessage | VideoMessage; interface TextMessage { type: "text"; content: string; } interface ImageMessage { type: "image"; content: string; // a URL to an image caption: string; } interface VideoMessage { type: "video"; content: string; // a URL to a video duration: number; // duration in seconds }
Or to clean it up further since content
is common to all:
type Message = { content: string; } & (TextMessage | ImageMessage | VideoMessage); type TextMessage = { type: "text" } type ImageMessage = { type: "image" caption: string } type VideoMessage = { type: "video" duration: number }
In this scenario, type
is the discriminant property to differentiate between each message type.
So now, if we tried to declare a type:
const message: Message = { content: "some-image.jpg", type: "image" };
We get the following type error:
Type '{ content: string; type: "image"; }' is not assignable to type 'Message'. Type '{ content: string; type: "image"; }' is not assignable to type '{ content: string; } & ImageMessage'. Property 'caption' is missing in type '{ content: string; type: "image"; }' but required in type 'ImageMessage'.
This correctly tells us that when we use the type
as "image"
, we need the caption
assigned.
So to correctly declare the message in this instance, we would write something like:
const message: Message = { content: "some-image.jpg", type: "image", caption: "This is some image" };
And our Type errors will disappear! 🎉
As you can see, it's pretty easy to create different dependent properties conditionally using this method.
If you play around with the different types, you'll see the requirements change depending on which of the type
's you add.
Follow me on Twitter or connect on LinkedIn.
🚨 Want to make friends and learn from peers? You can join our free web developer community here. 🎉