Have you ever heard about TypeScript types narrowing? Have you ever seen such an error in your TypeScript code:
Even tough, you really knew that the name
property exists on the person
object? How did you solve this problem? Did you cast the variable to the expected type?
If the answer to at least one of those questions is yes, you might read on. There are better ways of determining the type of local variable in TypeScript. What’s more, TS compiler does a lot of stuff for us by leveraging types narrowing. In this article, we are going to see how it works and how it can be useful ?
Table of Contents
TypeScript types narrowing – what is that?
One of the goals of TypeScript is to make writing JavaScript code better and more secure by adding the typing system. However, we don’t want the typing system to be problematic and making us to use the dirty haxes like all the time casting our variables explicitly, do we? ?
First, we need to understand that TypeScript does a lot of stuff in order to ensure we work with the most specific type as possible. Basically, if the exact type of local variable can be determined, TS compiler tries to do that.
Imagine such a function:
In vanilla JS, such code would of course compile. But in our case, TypeScript sees a problem:
The compiler is right here. Our parameter is either string
or number
, so the length
property may not be available if someone passes a number
.
You might have already been in such situations. What did you do? Well, sometimes when the code is more complex, and you just know that the variable contains the requested property, it might be tempting to cast:
Of course, we will satisfy the compiler here. But does that make our code any more safe than the previous version? Not at all.
In our example, based on the code we have written, TypeScript is not able to determine the exact type of variable
. We can help it by using type guards. We will talk about them in details in a separate article. However, for now it’s enough to know that the language has some built-in type guards – one of them is typeof. Let’s use it in our function:
That’s cool, TS compiler doesn’t complain anymore. But how does it work? Our code is still accessing variable.length
directly, there’s no casting there.
Notice what your IDE tells you when you hover your mouse over the variable
in the first if branch after checking if the type is string
:
and also what it tells you when hovering over variable
in the else
block:
Wow! The IDE magically knows what’s the exact type of our locally scoped variable! ?
This magic is called types narrowing. TypeScript compiler does many things to ensure that the type of local variable you work with is of the most specific type possible. It is based on the complex analysis of the source code, especially the code surrounding the place in which the local variable is used.
in keyword in types narrowing
Let’s take a look at this code now:
Let’s say we have a variable which may either be Employee
or Manager
:
In that place, TypeScript can’t say whether person
is Employee
or Manager
.
Maybe you’ve used the in
keyword for determining types before. Types narrowing also works with in
keyword:
As you can see, TypeScript analyzed the flow, and it noticed that in the first if
block we check whether the person
object contains the employees
property. If it does, the compiler known it must be Manager
, not Employee
. Cool, isn’t it? ?
Narrowing and equality operator
Another interesting application of TypeScript types narrowing is using the equality operator:
Notice how TypeScript determines the type of variables based on the ===
operator. If both parameters are equal, they must be both string
s.
TypeScript types narrowing – summary
Narrowing in TypeScript is a very power feature of the language. I just showed you some simple examples, so you can grasp the idea and notice how much TS compiler does to ensure your type is the correct one.
If you’re interested, you can read much more about this in TypeScript docs. It’s however worth knowing how TypeScript compiler works and how you should properly check for types in your code. Remember that using any
or explicit casting should be your last resort ?