Discriminated unions in Typescript: Why is it so good? š¤
This article is also available in russian.
Hi! ā
In this article, I want to talk about such a wonderful thing as discriminated unions in typescript (hereinafter referred to as DU). Iāll share with you why they are so useful and why I love them so much.
Here are a simple explanation and documentation if you are not familiar with DU. Also, for a complete understanding of the article, itās advisable to know the concepts of regular union(|) and intersection(&).
Why is it useful?
Let me give you an example. Letās imagine that we have our own culinary blog. Blog visitors can leave comments on posts. The author of the comment can be anonymous, or pre-register and become a full-fledged user with a name and an avatar.
This is what the comment model looks like:
The comment authorās model IAuthor
can be described in various ways. Keep in mind, that only users can have a name and avatar, which they provided during registration. Anonymous remains anonymous (sorry for the tautology š).
Attempt #1
Everything seems to be fine. name
and avatarUrl
are optional, which means that they may be absent. Why? Because the user can be anonymous. But is this the only reason? No.
There is a scenario that everyone forgets about ā a server error. Maybe someone accidentally deleted the name of a particular user from the database. Or it wasnāt saved during the registration at all. S**t happens š¤·āāļø.
The question is how do we respond to these two scenarios. If the author is anonymous, you can put the āanonymā label above the comment text. In case of violation of data integrity on the server, you can put āunknownā, but at the same time draw an avatar. Moreover, you can send a message about invalid data to the log server, which will help you quickly find and fix the error.
Since we react to these two scenarios differently, we must distinguish them somehow. Letās try further.
Attempt #2
The added kind
field explicitly tells us the type of the author, and as a result, what to do if name
is missing.
But there is a problem. Typescript doesn't force us to make sure that the author is a user before we reach for the name
in code. The name
field is always available. Seeing this, a new developer may not even realize that there is a type of author who never has a name.
Attempt #3 (Solution)
So what should we do? Use DU!
Note that name
and avatarUrl
are no longer optional (?
is absent).
Now typescript doesnāt let us refer to name
without checking kind
. Since comment.author.kind === āuserā
condition is true, the author is not a superposition of anonymous and user inside the if
anymore. Typescript treats him as a user, allowing access to name
and avatarUrl
fields. The if
ātrickā is called type narrowing, and the kind
field is discriminant.
Discriminated unions + intersection (&)
In all of the examples above, the kind
(discriminant) was the only common field across all model variants. What if there are more common fields? In order not to duplicate them in each variant, we can āfactor them outā using intersection (&):
Conclusion
DU allow us to describe the structure of variadic models very precisely, instead of mixing all the fields into a single āFrankensteinā model.
IMHO, DU are often underestimated and rarely used in product development. I think the reason is that there is no such feature in the majority of popular programming languages. Therefore, developers forget about DU when they can come in really handy or do not know about them at all.
I highly recommend taking a look at the second part of this article, where I share real-world examples of DU use!
Write beautiful code! Thanks for your attention š