The Hidden TypeScript Hack You Need to Know
How to Loosely Union Literal Strings
Hey there TypeScript enthusiasts.
I will not waste your time with a long introduction today, let's get straight to it!
1. The Trick
If you have been using TypeScript for some time now, you will have likely come across union types.
Union types take this form:
Now if you annotate a variable with the Fruit
type, you will only be able to assign it either apple
or orange
.
This is cool, but there are other fruits out there like bananas, berries, and tomatoes.
And please don't debate this in the comments, a tomato is a fruit not a vegetable.
Obviously, extending the the Fruit
type for every new fruit is not feasible, and therefore we need another solution.
You may be tempted to just union the Fruit
type with string
as follows:
And you may expect this type to include apple
, orange
, and every other fruit.
Wrong.
This actually evaluates the type of Fruit
as string
, and therefore you will lose your typesafety for any existing fruits!
So how do we solve this problem? Let's find out.
2. Using The Trick
To make this trick as clear as possible, let's use a contextual example.
In this example, we are defining a function that takes in a string argument representing an authentication provider you can use to sign into an application.
E.g. "google"
represents Google sign in.
As the code says, we can only input "google"
or "github"
, but what if we need to extend this function to accept google
and github
with typesafety, but also accept additional providers like resend
or facebook
.
Well, we have already discussed that adding | string
is not the right approach, so what is the right approach?
It may seem awkward, but we will go through it in detail.
Here is the code to solve this problem:
Instead of adding union string, we add union string & {}
. But why?
Well typescript's type checker is bad at distinguishing between the string
type and literal string types (e.g. "apple"
).
On the other hand, string & {}
is a type that represents all possible strings except null or undefined values, hence the intersection with the {}
type.
In other words, string & {}
is a more stricter version of string
, and therefore will preserve the typesafety for any existing literal strings, but still accept additional strings, similar to a "catch-all" type.
Here is the updated authenticate
function demonstrating the application of this trick:
Conclusion
This trick is very useful if you are incrementally adopting new authentication providers into your web application, and you don't want to update the type definition each time.
Of course, the most maintainable approach is to keep the type as exact as possible, therefore you shouldn't use this TypeScript trick religiously, since you will be slowly descending back into JavaScript territory.
But in certain cases, like the authentication provider example, this could be very useful for developer experience, and therefore has it's place in TypeScript.
If you enjoyed this article, please make sure to Subscribe, Clap, Comment and Connect with me today! 🌐
Want to ship code like a hacker? Visit Next Inject today!