- Now we're gonna get to some relatively complex
typescript stuff by talking about type predicates.
Let's take a look at this code real quick
for a simple example and then the next example
I'll show you a little bit more of a real world example.
So here we have a type of person and to-do
the person has a name and the to-do has a title.
Then I have a print function which is taken
in an object that could
either be a person or it could be a to-do.
And then I'm just checking to figure out
if they have a name property.
Well that means they're a person.
So I'm gonna log out the name
otherwise I'm going to be logging out the title.
As you can see, the object down here is the to-do type
and the object in here is the person type.
And that's because this, if check is doing all
of that fancy narrowing for me, now this works great as is
but as soon as I make my code a little bit more complicated
by changing this to an unknown type
because I don't actually know what this thing is
now I have problems because you know I first
need to check to see if this object is even an object.
So I can say type
of object is equal to object and it has a name property.
You can see printing out this name works fine
but it doesn't know what this object type is.
It doesn't know that it is a person in our case.
And same thing down here, it doesn't know
that this is a to do, which is not obviously that great.
Another problem with this, if we go back
to what we had with our code before is imagine
that this if check is much more complicated
it maybe has like six or seven things it needs to check
for to determine if this is a person
versus a to-do, well to do all of that inside
of this if is not ideal 'cause it takes up a lot
of space and if I need to make that check in other places
I now have to copy and paste that code a bunch of places.
So you may think
well I'm just gonna create a simple is person function
and this is gonna take an object that could
either be a person or a to-do and it's gonna return
to me whether or not this is a person or not.
So I'm gonna take this exact same if check up here
I'm just gonna return that if this is true
it's a person if it's false, I'm using a to-do so
here I'll just call is person and I'll pass
in my object but I'm getting some TypeScript errors.
And the reason
for that is typescripts not smart enough to look
at each of your functions and figure out what it's doing.
So it doesn't look at this function.
All it looks at is your code right here.
And the code right here is essentially just saying okay
I do some random if check
I don't know what happens in there
and then I'm trying to access my object.
Well it could still be a person or to-do,
type script's not smart enough to know which one it is
at this point.
'Cause again, it can't look inside this function.
So when you extract these like
if checks to determine what type things are into a function
TypeScript doesn't know
that you're doing a type narrowing here
and you need to help it out by telling it.
You are doing type narrowing.
And that's the whole idea behind a type predicate.
Essentially when you have a function that returns a Boolean
but it also narrows the type of something
then that's called a type predicate.
In our case we're returning a Boolean true false
if this thing is a person or not.
And I'm going to be narrowing it
down by saying that it is specifically a person object.
So to take this function and convert it to a type predicate
all we need to do is define a return type.
And this return type specifically is going to
name whatever variable we're trying to narrow
which in our case is this OBJ variable.
That's variable right here.
And then we need to say is by saying
that we are saying it is a specific type
and we're saying it is a person.
So if this value here returns true
so if this function is true, then it means
that this OBJ variable is going to be a person.
So whatever I pass into this is person, if it returns true
then it knows for a fact that that variable is a person.
So it allows TypeScript to do its type narrowing
by you explicitly telling it what the type of that thing is.
Now there are a few downsides to this
though and that is that it's kind of similar
to using the as property and just manually casting something
because this does not have to be a correct assertion.
I could come in here and say, you know what
it's actually going to be a to-do if this is true.
Now as you can see, this is actually lying
to TypeScript because if this is true, it's a person
but I'm telling it it's actually a to-do instead.
And as you can see, I'm getting errors in my code
that I shouldn't be getting because I'm lying to TypeScript.
So it's really important that if you end up
using these types of function predicates
or these type predicates in TypeScript
that you're very careful to make sure
that whatever you cast this thing as
in the is section is a hundred percent correct
and will never be wrong
'cause otherwise you're going to be lying to TypeScript.
So really how these type predicates work is you
define a normal function that takes in some type
of objects or variable that you want to narrow the type of
then you return true
if the type should be narrowed or false
if it should not be, if it returns true, then
that means the type will be cast to whatever you set in
this is section right here and if you return false
nothing at all is going to happen.
Now to look
at a little bit more advanced real world example
imagine here that we have some priority array
and then we have a to-do which is
either a title or a description.
And then let's say I wanna do something
with that to-do whatever, it doesn't really matter.
There we go.
So inside of this function I'm trying to do something
with the to-do and in this case I wanted to determine
if my description is describing a priority
because maybe inside of my to-do, you know I give it a title
of like do laundry and then in the description I just write
you know this is a high priority to-do.
So I write high.
So if I want to check to determine
if my description is just a generic description, such
as like add whites, then darks or if I wanna determine
if it's actually a specific thing, like high, medium, low
well this is a great use case for these type predicates
'cause this description is either going to be a string
or it could be this more specific priority.
So what I can do inside of here is I can just create a quick
if check and I could say if is priority, I'll pass
in my to-do description just like that.
And then let's come down here and define that function
is priority, which is going to take in a description
which is a string.
And here I just wanna return if my status is
what I call this priority not status.
There we go.
Priority dot includes my description, there we go.
So if my description is one of these three different things
then I know that it is a description.
So I wanna say that
in that case my description here is going to be
a priority, but I don't have a priority type yet.
So let's just create one.
The priority type is just going to
be using the priority variable priorities.
There we go.
I should probably use this plural in all these cases.
There we go.
So it'd be priorities number, I wanna get the type of that.
There we go.
So this priority type right here is just going to be one
of those three different types
and I just need to make sure I add as constant
and now you can see it's either high, medium or low.
So I wanna say
that this is going to be one of those different priorities.
Now immediately you'll notice we get a bit of an error.
It's saying
that description here is not high, medium, or low.
So it doesn't know, it can't call includes essentially.
So a lot of times when you're doing these type
predicates you may need to do some casting inside of here.
So I can say I wanna cast this to a priority.
Essentially what I'm saying here is that inside
of this function I'm going to assume
that description is a priority and then do all my checks.
And this is pretty easy and safe to do just
because I'm not like casting this to be used other places.
It's only cast inside
of this specific function to make it work.
And then I'm just going to let my code assume
that this is a priority run and if it returns false
then my predicate here that's saying, is this a priority?
We'll return false and it'll know
that this is not actually a priority.
So now in this case, if is priority
we can check our todo.description
and you'll see now it is either high, medium or low outside
of this if statement inside this else section
our todo.description is just a generic string
because it's not actually a priority.
So this is one kind of example
of where you may want to use this.
Again, it's not something you use super often
and I would almost always recommend using other types
of type narrowing or something
like discriminated unions to solve this problem
if you can and only fall back
on these type predicates if you absolutely need them.
Again, the reason for that is because a lot
of times you need to use as within them
which I don't really like
and this is keyword to redefine the type of, this is a bit
of a as kind of thing because I could just say
you know what, actually this is a number and now you can see
that it thinks that this thing is going to be a number
or something like that.
And in this case, the reason it's not letting me assign this
to a number is 'cause strings can't be numbers
but I could say that it's going to be the string SFD
and now you can see it's SFD
when in reality it's really high, medium, or low.