Type Guards in Typescript
Often times when I speak to developers who have just started writing typescript, they have begun to understand static typing. They understand that they can label a variable as type string
and then rely on the fact that it will be a string
within a function. There is however, another, rather large part of typescript that I don't see newer typescript devs use very often. Type Guards and their ability to branch code in the event a function takes multiple types are extreemly useful. So today, I'm going to explain Type Guards and why you should be using them.
A Type Guard is a special sort of function. It allows you to pass a value and receive a boolean
which is true
if the passed value is of that type. In javascript, Array.isArray
is a perfect example. Typescript would consider it a Type Guard as it returns true
in the event it is passed an Array
.
To begin our explanation, let's say that we have a base interface of Animal
and we are extending it for the interfaces of Cat
and Dog
.
As you can see in our Animal interface, species
is a string. However, when extended we've explicitly defined that species will either by Cat
or Dog
. In the real world, it's unlikely that two extended interfaces would have a shared field with an explicit value like this, but for our example, it's going to help explain a few things.
We can write a function to accept the Animal
interface, but because both Cat
and Dog
extend that interface, they are possible to pass as well. Let's create our basic function.
If we create a Cat
and pass it in, you can see that no errors are thrown because Cat
is an extension of Animal
.
Now pretend that we want to add some specific logic if animal is a Dog
. We could start off by assuming that every passed in Animal
is a Dog
. However, as you'll see with the below update to aboutAnimal
, You will get the error Property 'ownerName' does not exist on type 'Animal'
.
To fix this, we can create a Type Guard. Type Guards use the is
keyword in their return type to indicate if the value is of a specifc type. They can contain whatever logic you need to confirm with certianty that the type is what you are expecting. Let's create a Type Guard for Dog
This might seem reasonable as a Type Guard. You are checking the species is Dog
which is a key indicator of the type being Dog
. Unfortunately, it is possible that someone made a mistake elsewhere in your code and created an instance of the parent interface Animal
with the species set to Dog
. With this Type Guard, you cannot be sure that ownerName is not still undefined
.
To fix this problem, we need to look at what fields are unique within the interface Dog
and instead check for those. Luckily, cats view their owners as lesser (I have cats don't come for me) and as such while a Dog
has a ownerName
field, a Cat
instead has a petHumanName
so we can check for that. With our current intefaces, only Dog
has the ownerName
field.
Much better. If we now use that Type Guard in our about animal function. We'll see our error disappear as the code trusts the Type Guard to ensure that only Dog
values get through.
Type Guards are incredibly useful when you have functions that take extended interfaces or even multiple types ( eg. (val: string | number) => {}
) and you need to have branching logic depending on what's passed in. Especially if the types involved are complicated or you'd need to check multiple fields to confirm the type.
You can use the bang operator (!
) to negate the check and instead check if a value is not the given type (eg. Not a Dog
). This can be useful if you need to perform a type conversion to ensure type consistancy.
Let me know how you find using Type Guards in the comments or by @ing me on twitter @mikevdev. Make sure to share this article with anyone you think might need to learn about Type Guards.
Webmentions
What's this?This site uses Webmentions to handle likes, comments and other interactions. To leave a comment, you can reply on Twitter, GitHub, or Reddit. While the site will count likes from any source, only twitter likes are currently displayed in the facepile above.