Development
Do you want to improve your coding experience and understand Typescript on a deeper level? Yes? Then continue reading. In this article, I will introduce you to a collection of some less known and advanced tips, tricks, and possibilities of Typescript. But before I go on, let’s define Typescript first.
In a nutshell, Typescript is Javascript with types. A more sophisticated description would be that it is an open-source language that builds on JavaScript by adding type definitions. Think of it as an advanced and upgraded JavaScript. But keep in mind, you do not have to use Typescript to its full extent - although I strongly suggest you do.
Now that we have recalled the definition of Typescript, let's get down to business. I will explain utility and conditional types through examples and give you solutions for possible coding problems. If you have never worked with Typescript, please come back to this article if you ever decide to give it a try.
Don't miss out on this! Indexing interfaces/types is a very simple, yet useful feature. It allows you to select a type as you would select a property of an object. I'm sure you're already familiar with it, but in case you're not, I have to mention that knowledge of it will help you further down the line of this article.
Take a look at the following code:
The variable “someDateOfBirth” has a type that we took from the “User” interface.
On the official Typescript website, you can find a list full of utility types documentation pages. If you`re not familiar with them, I recommend you to read them. There are also some scenarios in this chapter on which I would like to focus.
The “Pick” utility is really easy to understand. It picks up only those properties from the interface you specified and you can create a new type including the chosen properties.
Here is an example from the “User” interface, where we searched for “name” and “lastName”:
It looks all nice, but now imagine a function like this;
If you take a look again at the interface, you will notice an issue - the “email” property is an optional property, marked with “?” , which means “Pick” will also “pick up” that “optionality”.
So this code would do the job:
The problem with this code is that we will never send this “John” user an email. Luckily, there is a way to fix this, so I will give you three possible solutions for this error:
It will force you to make sure that the user has an email before passing it to the function. After that, you will need to remove the “if” check because now you know that the user.email will always be defined. When your function has a pattern like "doSomethingIfOtherThing", you should rethink your approach. I intentionally went with this example to show you that a condition like this would never be false.
Note this - unlike the “Required” approach, you can make it possible to pass a user with an undefined value. It will force you to have that property even though it is unspecified. Take a look at this code:
You can use this type to repeat the first solution (email can never be undefined) with less code repetition:
Or you can use this code to repeat the second solution (email may be undefined, but you will further emphasize this) with less code repetition:
If you still have no idea what is going on here, don’t worry. Continue reading and I will explain everything later in the article.
Let’s say you have this function:
Here, you don't have to pass the picked object to the function if you want to call it. So, don't do this:
Instead, you can just pass in full user:
This only works if you are supplying a “wider” interface than what is required, although even the “smaller” one doesn't have to be derived via “Pick”. But watch out! It will also work if you provide a completely different type, but “name” and “lastName” must match.
So here is the complete code:
Imagine a scenario where you don't want a function to do anything with "externalUser" - it would be better to avoid “Pick” and demand a full type. Although this is not an issue most of the time, it comes in handy.
Record utility allows us to create an object with predefined keys and values. Check this out:
The issue appears when we use a “less specific” type for the keys (first parameter of Record) such as “string”. Here is an example:
An issue with this function is that functionsDictionary.bar doesn't exist, but as far as Typescript is concerned, we could call it.
Okay, now what would be the solution here? Well, there are some advocates to make “Record” properties possibly undefined by default. If you need that kind of behavior, you can create it yourself using the “Partial” utility type. “Partial” is the opposite of “Required”. Why? Because it makes all fields optional ergo, maybe undefined. It will force you to treat every possible key as maybe not set. So, the code would look like this:
Note that you have a similar issue with arrays:
There is a long debate on this within the Typescript community if this should be the default behavior. One argument is that you should know the length of your array before indexing, but this is something I will leave for a future article.
Okay, remember this code from a bit earlier?
Here is a quick overview of things that are used for creating this type:
Let's move on to explaining each one of the above-mentioned topics. I'll keep it short, promise.
Generics
As the name suggests, they allow you to take a generic type and do something with it - the Pick, Require, and Record types I used above are generics. This is the most basic example of it:
It is the equivalent to writing:
for each of the foos.
keyof
Very simple: it takes all keys of a given type:
Mapped type:
Simply said, “Mapped type” creates a type where both keys and values are predefined:
You may have noticed the in a keyword and slightly different syntax:
Essentially, what I am doing here is defining what the type of value each property P of Required<Pick<T, K>> should be.
Let me explain the solution as simple as possible. Firstly, I created a new type that takes an interface and keys you want to “pick” properties from. That type works by passing the interface through Required, which will remove the optionality from the key. In the next and last step, we will just index the T with a P. It will pick up the type directly from the T and not from the Required<Pick<T, K>>, so the undefined possibility is still being preserved while optionality is not.
I know it’s a lot of new information, so let's take a break here. So far, you have learned what Pick utility can do, how to fix the “Pick” problems, how to use the string | number | symbol in Record utility, and much more. In the second part of this article, I will show you how to make types change depending on some conditional types and explain how to work with type unions to leverage things you can do with them. Stay tuned. :)
Mihael is a Frontend Web developer at COBE. When he’s not mastering his frontend skills in React and TypeScript, he's either reading books or watching movies.