- So far in this course,
we've talked a lot about how you can create types,
modify types, and assign types
to different variables and functions.
But in this section of the course,
I wanna talk about how you can take those types
and utilize them in your code with type narrowing.
In this video in particular,
I wanna be talking all about type guards,
which were an incredibly powerful feature of TypeScript
that allows you to have fairly vague types
that you can make much more specific
in your actual JavaScript code.
It's really amazing how this works.
Let's take for example, this code.
We have a Todo type that has a title which is a string.
It has a priority of High, Normal, or Low.
We have an isComplete boolean.
We have an optional description that's going to be a string.
And then we have a dueDate.
And this dueDate is either a Date
or it's a string in some type of like timestamp format.
We don't know which one it's going to be,
we just know it as one of these two different formats.
Working with this could be kind of tricky
because we have things that are optional
and things that can be multiple types,
but with TypeScript,
it ensures that we make sure we check
for all these different things
and handle every single possible use case
and it makes it really easy to do.
The very first thing I wanna talk about
is the most simple type of type guard,
that is just checking what type something is.
For example, this dueDate can either be a string or a Date.
So we can check what the type of it is
to determine what we're going to do.
'Cause right now, if we come into here
and I just do a quick consult log of our Todo.dueDate,
and I hover over our dueDate,
you'll see it as a string or a Date.
Now what I can do if I want to do some type of modification,
like I want to add seven days to this date
or something like that,
well, if I tried to do that normally,
like I tried to access my Todo.dueDate,
and then I tried to like get the days on this or something
that you can do on a Date,
you can see this isn't working
'cause a string doesn't have these methods.
So what I first wanna do
is I wanna check to see, hey, is this thing a string?
So if my Todo.dueDate,
if the typeof this is equal to a string,
well then I'm going to do something else
inside of my code here.
And if I just move my console.log up into here first
and I hover over dueDate, you'll now see the type of this
has actually changed to string.
And that's because TypeScript is smart enough
to look at your JavaScript code and it says,
"Hey, I have checked to see
if the type of this thing is a string.
And if that's true, well, it means, obviously,
my dueDate is a string at this point."
So it's going to change that type for you automatically
to a string.
So now, if I try to do something on this,
for example, you can say I have autocomplete
for all the different string methods out there,
which is really great.
If I were to come in here with an else statement real quick,
and I just move my console.log down here instead,
you'll now see that when I hover over it in the else,
you can see that this dueDate has a Date type.
And if I try to do here,
you can see I get all of the different Date autocomplete.
For example, I could get like the Date
or something like that.
So when it comes to checking types,
probably one of the most common things
you're gonna do is you're gonna have some if else statements
or different control flows that use this type of keyword
which is built into JavaScript
to figure out what the type of something is
to narrow down these types
where there's multiple different types something could be.
Another thing that's really common
is using the instanceof keyword instead.
For example, I cannot check for Date inside of here.
You can see that this only allows me
to check primitive types,
such as booleans, functions, numbers.
It doesn't allow me to check for something like Date
because that's not a primitive type.
So instead what I need to do,
if I wanna check to see if this is a Date,
I would want to replace my equal signs here
with the keyword instanceof,
and then I would pass it essentially the name of a class
or like object type.
But in my case, Date is a particular object class
that this can be something of.
So now if I move my dueDate up into here
and I hover over this,
you can see that this now is the Date type
because I'm checking to see if it is an instance
of a Date object.
And if it is a Date object,
well, now we know the type of this is a Date.
And if I were to put a console.log down in the else,
you'll see that this version right here is a string
because it knows if it's not a Date
then it must be a string.
That's my only other option.
If I were to add other things into here, like number,
you'll see now down here that this dueDate
could be a string or a number
'cause it could be either one of the things
that aren't Date.
While up here, it still can only be a Date
'cause we've checked explicitly to make sure it's a Date.
Now, this may sound kind of confusing,
but the way TypeScript handle all this
is actually relatively intuitive.
Essentially, just look at your JavaScript code,
and if your JavaScript code is saying this thing is a Date,
well, then TypeScript will know that it's a Date.
And if it's saying this thing is anything but a Date,
it's going to follow that control flow for you.
So I could come and here with another else if,
and I could say if the Todo.dueDate,
if the type of it is, let's say,
equal to a number this time,
now, you can see that my dueDate here is a number.
And if I come in with a final else,
I just copy down this exact same log.
You can see that it's now a string
in this particular scenario.
Now, speaking of how TypeScript
is smart enough to handle all this fancy control flow stuff,
let's bring this back to just Date or string
and I'm gonna really simplify our code.
I'm gonna remove everything but this check
to see if it's a Date.
And instead of doing a console.log here,
let's just say I have some code that does something.
This is gonna be like extending my Todo or whatever it is,
and then I'm just gonna have the return keyword.
So my function is ending right here.
And then down here,
let's say I want to console.log out my Todo.dueDate again.
What do you think the type of it's going to be here?
Well, TypeScript's smart enough to know that if it's a Date,
it went through this code block and returned,
which means that it's impossible
for it to be a Date down here.
So when we hover, you can see that this is only possible
to be a string at this point.
So again, TypeScript is just following the control flow
of your code and saying,
"Okay, all the dates are going to follow this path,
which means it's impossible for it to be Date after that,
which means it's going to be the only other option,
which in our case is a string."
This is really powerful
and it makes handling all these different type things
so much easier,
especially when you have multiple different types.
For example, a really common scenario
is when you have an optional type, like a description.
For example, if I just take my Todo.description
and I try to get the length of that,
well, I'm gonna get a bunch of errors
because this does not actually have a length property
because it could be undefined.
A string has a length property,
but a description that's undefined has no length property.
So I need to check to see if it's undefined
before I do anything.
I could use the same if check where I say
like if the Todo.description !== undefined,
then I can run this code.
And as you can see, I no longer get any errors.
I could even just say this simple if check right here,
'cause this is gonna make sure
that anything null or undefined is removed from it,
what I could do is even simpler
is I could just use this optional chaining syntax
inside of JavaScript.
What this is saying is if this thing is null,
just return null.
Same thing with undefined.
If it's null or undefined, return null or undefined.
Otherwise, run the code afterwards.
So if the description is undefined,
it means that all the code after the question mark
doesn't exist at all.
But if it is something that's not null or undefined,
for example, it's a string,
then it's going to run all of this code afterwards.
So this is a great way to essentially narrow down your types
without having to do a bunch of if else checks
because it's just doing this one question mark syntax
that handles all of that for you.
If you have null or undefined in your code,
this is a really common way to check for that.
Now, sometimes when you're dealing with null or undefined,
you may not want to have
all these optional checks everywhere.
For example, let's just write some more code down here.
We're gonna get a form by saying document.querySelector,
and we're gonna get some like form element
or something like that.
And now I wanna add an event listener to this.
Let's just say we're gonna add
a submit event listener to it,
gonna take in a function that does nothing,
just so we can see what the code looks like.
You noticed automatically as I was typing,
TypeScript added this question mark in there for me
because essentially, when I'm query selecting,
this could be an element or it could be null.
And I could specifically say, since I know this is a form,
I could say that it's going to be an HTML form element.
So this could be an HTML form element or it could be null
because it could try to query my document
and not actually find this element.
Now, most often,
you probably want to leave this check in there
because, obviously, for some reason
your form's not on the page,
you don't want your code to error out,
you just wanna skip this section.
But maybe you know better than TypeScript
and you know for 100% fact that this document element
for the form is always going to be there.
You know it's never gonna be null.
Well, what you can do
is instead of putting a question mark here,
you can put an exclamation point,
and that essentially tells TypeScript
it's impossible for this thing to be null.
So this is not anything with JavaScript,
this is only going to be a TypeScript feature.
When you compile your code,
it's essentially just gonna remove this exclamation point,
but that just tells TypeScript,
it is impossible for this thing to be null.
So it doesn't give you an error
when you try to access things on it.
You can put this question mark anywhere you want.
For example, I can put it at the end
of this query statement right here.
And now, I'm telling TypeScript
that the return value from this is never going to be null.
So when we hover over our form,
you can see it is no longer null at all.
Now this may seem great and you may think,
"I'm gonna put this everywhere that I have null
that I have to deal with."
But in reality,
you probably shouldn't use this exclamation mark syntax
very much at all.
The reason for that
is because it kind of overrides TypeScript,
just like how I talked about any is a bad idea
since it overrides TypeScript
and kind of brings you back to JavaScript.
Doing this exclamation point
essentially removes all of your checking
when it comes to null or undefined.
So if you have a value that doesn't exist
and you put this exclamation mark there,
TypeScripts not going to know it doesn't exist.
So it doesn't give you
all the same error checking you want to have.
Now, of course, there are some niche scenarios
where this is really useful to put in there,
but for the most part,
if you have something that could be null,
it's better just to leave it
as something that could be be null,
unless you are 100% sure it's impossible to be null.
And one common scenario for that
is when you're doing things like querySelector statements.
Now, kind of one of the final things
I wanna talk about when it comes to control flow
that's really common you'll see in TypeScript
is using switch statements to narrow down your types.
For example, this priority
can be either High, Normal, or Low.
So we can narrow down the actual type of it
using something like a switch statement.
We can do the same with an if statement,
but it's really common to do with a switch statement
when you have something like that.
So here is our priority.
There we go.
And let's say we have a case here
where the priority is gonna be High.
You can see our auto automatically get autocomplete,
which is really nice.
I can console.log my Todo.priority.
There we go.
And if I hover over the type for this,
you can see the type is being hard coded
to the literal value of High
'cause it knows when I'm inside this case statement,
it's obviously going to be the High type.
Now if I break outta that
and I come in with a new case,
you can see I only have two more options left.
As you can see here, Normal is now my next option.
So if I do a quick console.log here and we break outta that,
you can see when I hover here,
this has a priority of Normal.
Now, if I just copy this down one more time,
we'll see that we have our Low priority
as the last option we can add in here.
And you can see the priority here is Low.
If I try to add in another case,
you'll notice I have no more autocomplete options,
and that's just because there are no more cases left.
I've already used all my different cases.
This is a really great way to check all your different cases
and narrow down different types
based on different things in your code.
It's something really common that you'll see
when you're dealing with unions, for example.
This is actually something that's really commonly used
for something called an exhaustive switch statement,
which is what I'm gonna cover in the next video
because it rolls perfectly into the never type.