January 2nd, 2023 × #typescript#webdev#javascript
TypeScript Fundamentals × Type Narrowing, Guards, and Predicates
Discussion on TypeScript fundamentals like type narrowing, guards and predicates which narrow types from general to specific.
- Response types success or failure
- Narrowing from wide to specific type
- querySelector can return undefined or element
- Narrow type from multiple to one option
- Type guards do the narrowing
- typeof checks type
- Check if property exists on type
- Truthy falsy checks
- Custom utility methods for checking type
- If/else narrows types
- Predicates explicitly narrow types
- Predicates return narrowed type
Transcript
Announcer
Monday. Monday. Monday.
Announcer
Open wide dev fans, get ready to stuff your face with JavaScript, CSS, node modules, barbecue tips, get workflows, breakdancing, soft skill, web development, the hastiest, the craziest, the tastiest web development treats coming again hot. Here is Wes Barracuda Boss and Scott
Scott Tolinski
CSD. Well, come to syntax.
Scott Tolinski
On this Monday hasty treat, We're gonna be talking about TypeScript again. We love talking about TypeScript over here. We're gonna be talking about fundamentals in TypeScript, it. Some lesser known but important features, things like type narrowing, guards, and the predicates.
Scott Tolinski
With me as always is Wes, it. The Tony Hawk of TypeScript boss.
Wes Bos
Hey. Hey.
Wes Bos
We've had a hell of a morning. We we both had, like, we both had, like, probably 3 independent issues with Internet audio and street video streaming. Technical issues. It's just, like, literally I had a problem with my camera and my audio, and Scott had a problem with his Internet switch
Scott Tolinski
And his video now, which is just Yeah. Now all of the sudden my video has just died. After all of that, we were just like, alright. We're good. We're good to go. And now My video has died, so, well, I'll get that back online. You know what? It's time for it's time for Christmas holidays. Yeah. It's I just got a notification from you that says I'm gonna restart my routers,
Wes Bos
That was 15 minutes ago. Something is going on with the Internet today. We are ready for Christmas, but this is this is one of the last ones we need to record. And it's kind of an exciting one. It's about TypeScript fundamentals, type narrowing, type guards, and type predicates, which is it. Funny. The 1st time we tried to record this, Scott was like, I don't think I've ever used the word predicates in my life. And I was like, I literally just Googled that What does predicate actually mean? Because I've always just called it, like, a type guard in TypeScript, and I looked it up in the docs. And it's it's The part where your return is is called a predicate. So we'll get into that.
Wes Bos
You're ready to rock there, Scott? I am so
Scott Tolinski
it. Incredibly ready to rock here. I could get my video going. Yeah.
Scott Tolinski
I had heard the word predicates involved in, like, English, but never within TypeScript or within programming, so to say. So yeah. No. I'm I'm I'm ready for this. It. Let's talk about it. Type narrowing, type guards, and type predicates.
Scott Tolinski
The those are all similar topics. I I I take it because I I know what Tight narrowing and type,
Wes Bos
type guards are. Yes. Yes. So predicates is is just pretty much part of that. All of this stuff today, they're not, they're not different things. They are all part of narrowing types. So let's talk about that. So for this podcast, we have an example that is going to be helpful for you to visualize it. So, a type in TypeScript, if you've never used TypeScript Before is sort of a way to describe the shape of a value. And in our case, we'll use an object. So we have a success object to which has a message On it that will say the request was successful or got 8 users or something like that. And then we have a value on there. So we have a message, Which is a string. And we have a value, which in my case, I'm just gonna say it's a number. Right? It could be any type. And then we also have a failure type, which has a message. So the message could be it it didn't work. And then we have an actual error, which will give us more information about when or what went wrong.
Response types success or failure
Wes Bos
Then, I I like to use this as a whenever I'm doing, like, a fetch request or something like that, you have a response type. And the response can be Either a success or a failure. And and that is what's referred to as a union. Basically saying it's one thing or another thing. It's a list of it. Of possible types. A response type is just a list of 2 types, a success type or a failure type.
Wes Bos
It's technically called the Humanating union. We'll talk about that in a little bit. It's not really what we're we're trying to focus on today.
Narrowing from wide to specific type
Wes Bos
So narrowing, narrowing is the process of Taking a type that has a few possible options or a type that is sort of wide, in making it more specific.
Wes Bos
So a if you have a type of number or string or you have a function that takes in a number or a string, sometimes before you can work with that value, You have to figure out okay.
Wes Bos
It it might be a number or a string, but what is the actual value? So narrowing would be say, okay. If it's a string, then go ahead and do string things with it. If it's a number, then go ahead and do number things with it.
Wes Bos
Our example earlier, it could be our response type could be a success or a failure, and we need to narrow that down Before we work with the specific success data or the error error, we need to narrow that down. Mhmm. I I just keep using my hands here. I'm saying narrow down. I'm I'm making my hands go wide,
Scott Tolinski
too narrow. You kinda look like the the visual to give folks a visual here, he's kinda doing the gesture of, like, Come on. Come on. What's going on here?
Wes Bos
A query selector.
querySelector can return undefined or element
Wes Bos
When you select something off the page, TypeScript can't, Can't guarantee that that value is going to be there. So it will always return to, HTML video element or undefined.
Wes Bos
And before you go ahead and do things on that video element, like set the volume, set the source, grab the the width and height. You need to check that that value is actually there. That is narrowing the type from Two possibilities, undefined or a video to 1, which is video element. A media a media element is another good example.
Wes Bos
Sometimes I write code that will take in either a video or an audio tag because those things are very much the same. However, there's a couple properties on either of them that are not shared between the 2. And if you wanna work with it, like dot video width is one of them.
Wes Bos
If you wanna get the video with, you need to narrow from video or audio to just video. So, basically, narrowing means
Scott Tolinski
us. Checking if something is of a specific type. Yeah. And this type of, thing directly comes about you know, when you're getting into TypeScript, oftentimes, You you're really like, okay. This thing is of this type. I know it is. And then in comes the the TypeScript Checker to say, hey. This property might not be on this type. And and you're thinking to yourself, wait a second.
Narrow type from multiple to one option
Scott Tolinski
I feel like I know what this thing is, but TypeScript is telling me that the data potentially isn't there.
Scott Tolinski
And so oftentimes, the 1st time that people pick up type narrowing in general is to, write code that ends up being, Like, what you might think of as being solid or secure code in a way, but ultimately isn't because TypeScript Tends to know better than you in terms of what you're actually checking for. So type guarding, like or type narrowing And type guarding.
Scott Tolinski
It's so funny these things, blend together in my mind a lot. They really come into play when you're trying to Really narrow down
Wes Bos
what a specific type actually is. Exactly. And if you you often will get this error, it. If you try to access a property on a value and it says property x or property value does not exist on type, And it will give you the list of types. And if you scroll down that list, because often it they're kind of indented.
Wes Bos
You generally, the last one in there says, We'll tell you. Okay. In my case, we're talking about grabbing a value which only exists on success, not on error. And it will tell you value doesn't exist on On failure, so and there's a there's a slim possibility that this thing might be a failure. So you need to handle that Before you go ahead and grab that value with an if statement or or we'll talk about how to do that in just a sec.
Type guards do the narrowing
Wes Bos
A type guard is the name for how to narrow something. So narrowing is just the idea of going from specific wide to narrow. A type guard is the actual code you write to, to do that narrowing for you, and there's many different ways to, to write a narrow or to check if something is of a specific type, and the there's here and there, there's different values. You wanna grab the first one there? It. Yeah. The first one is a type of, which,
Scott Tolinski
you know, it's a pretty standard JavaScript thing, but this is often used to make sure that it. What you're doing in an if statement is the type of the thing that you're expecting before you advance on into the next stage of things. You know? Everything has different properties and methods on it. So being able to say, if blank is type of thing, Then you're able to
Wes Bos
accurately guard against potential other types. Exactly. And a very simple type type guard for that will look like If, inputted value, let's say you you get a value from a, an API, and it is either going to be a string or a number. You just basically say, if my value, type of string, Then go ahead. And and at that point, if you hover over the variables, before that if, you will it will tell you string or number.
typeof checks type
Wes Bos
And then after that, if it will tell you it is just a string. And that is TypeScript narrowing the type for you and saying, okay. You are now in a a place where you can comfortably assume that this will always be a string.
Wes Bos
Next 1 we have here is check if a property on that type exists. So, basically, all of these type guards are going to be just regular JavaScript that you are used to checking. Yeah.
Wes Bos
Like, if if someone were to tell you, alright. We'll check if this thing is has a specific property or check if this thing looks like this. You there's There's many different ways that you can do that, and you probably have written those things a 1000000 times in JavaScript. So type of is one of them. Next one is check if a property exists on a given type, which is it will say, has own properties is what I've been using a lot. So you grab it And you say, like, if the response has own property value,
Truthy falsy checks
Scott Tolinski
then go ahead. And what that will do is it will check if it has a value on it, and then you can safely assume after that that it is a specific type. So the next one is just basic straight up JavaScript. By doing just a straight up Truthy or falsy check, which is something that you, you know, you should probably be cognizant of if perhaps, you know, values that can equate to falsie that aren't truthy truth or falsie values, like things like a number. Right? Like a number of 0.
Wes Bos
Number of 0 equates to false, so that might not be in your in your cards. You know, one thing you could always do with this is do that double a point trick, which some people hate, some people are fine with. Do do you ever do that, Wes? Yeah. So I have been using double estimation for a couple years, and I I kinda stopped using it because I I understand how truthy, falsity works. Mhmm. And then I made a I made a little TikTok of, like, what is this double? Because you you still see it. I do use Yeah. All over the place. Yeah.
Wes Bos
And a lot of people says said just use Boolean. Like, if you just use capital b, Boolean and pass The value, it's much more readable. And I was like, I don't know why I really didn't think of that. It does track. Yeah. Yeah. Yeah. So I it's it's a little bit more. I guess people don't like it because it's like bang bang. Yeah. But I understand how it works. So, I guess you just got to think about
Scott Tolinski
other people reading your code, you know? Yeah. Yeah. Because for those of you out there who don't know the the double bang trick just before, it. You do 1 bang or exclamation point in front of your variable, which will then turn it into a Boolean, it. But reverse it. Like, if it's a 0 and you put an exclamation point in front of it, that's going to equate to true because the bang is kind of like Turning a a Boolean value to its opposite. So then you do a second one in front of it. And what that does is the first one essentially converts it to a Boolean, and the second bang Converts it to the correct Boolean. Yeah.
Wes Bos
I I I always say bang, bang, you're a Boolean, which is basically taking the value and coercing it into it. A true bang bang and your boolean. Yeah. So this truthy falsy one actually bit me a couple days ago, because I had I would talk about this error message here or or error returned in a failure. I had that as a string, But I was checking if there was an error.
Wes Bos
However, TypeScript was saying that's not good enough. And the reason why that was not good enough is because There's a possibility that there would be an error, but the error would be a empty string, which would not would not be a safe enough type guard.
Wes Bos
So in that case, you would have to explicitly check if it is undefined or null, instead of doing just like if, response dot error because that would not be because there could be an error. But if it's an empty string, it would say there is no error, but it technically is an error. You know? So I was like, oh, that's that's a good one. I didn't wouldn't even have thought of that, and that's why you use TypeScript. Yeah.
Scott Tolinski
It's funny to me that a lot of these, in practice, they often feel Redundant or maybe occasionally annoying.
Scott Tolinski
Annoying. Yes. Annoying is the, the less a diplomatic word, but, yeah, I I totally, agree with that, where you're just like, meh, why do I have to do this? But at the See, end of the day again, I think TypeScript is smarter than you in many of these regards, and it's it's just looking out for you. Totally. I I also ran into
Wes Bos
Another thing with this the other day, type guard, which was, I use a regular function, and I I basically selected something off of the page and the line underneath it, I checked if it was there. So I would narrow it down from An element or no just to an element. Right? However, I wrote a function underneath that on the 3rd line. But because functions are hoisted, If there's possibility that that could be called before you Yeah. Select the element. So it was like, uh-uh.
Wes Bos
Yeah. So you have to check inside of the function again.
Wes Bos
So the the solution to that was there's a couple of solutions. 1, just pass the element into the function, instead of Reaching outside the scope to get it, which is what I would do, I think, typically Yeah. Anyways. Because then your your functions are more pure. Right? Yeah. That's kinda how I roll. Yeah. I I think that's This is probably your best bet. And the other way is you could just use a type of function that is not hoisted, which is a function expression where you say Const function name equals function, which I don't I usually don't like those, but that that's, those aren't hoisted like regular functions.
Wes Bos
Alright. Next 1 we have here. Another way to type guard is using a utility method.
Custom utility methods for checking type
Wes Bos
So, number dot Is number. Well, that's actually not a method in JavaScript, but you could you could make that. You could go on NPM and and grab a a utility method.
Wes Bos
You could use an is error method that specifically checks for it or is is not a number. So that's that's there's one there. And then the last 1 we have here is use an if else a statement. If else statements will narrow for you.
If/else narrows types
Wes Bos
So you say let's say you have a property on an object.
Wes Bos
I always use currency.
Wes Bos
Currency could be CAD or US or or EUR.
Wes Bos
You can say if currency equals CAD, Then do this thing. And then on the next else if, it knows the possibility of currency is not gonna be CAD because you're a check for it. It's the last 2.
Wes Bos
And then you could you could have 1 more else in there, and it will know. Okay. Well, at this point in time, I've narrowed the type down to just be e u r, at that point, the same thing with switch statements. They work exactly the same way. So next step is type predicates,
Predicates explicitly narrow types
Scott Tolinski
and The note here is that type predicates use the is keyword to narrow. Now I'm super interested in what a predicate here is in this regard and what exactly this means. Yeah. So,
Wes Bos
we just talked about ways to type guard, which is type of, if statements, Utility functions, all of those things that we just talked about, those are type guards.
Wes Bos
And often, you have to Those type guards need to explicitly state that you are narrowing the type, because sometimes just an if statement is is not good enough.
Wes Bos
And it it sometimes is weird. It's like, oh, why didn't TypeScript catch it in that case? So if that is the case, then you have to use what's called a type predicate.
Wes Bos
Or sometimes you just simply want to take all of the type guards that we just talked about and put them into a nice reusable function that you can use throughout your entire code base, like is error or is success response.
Wes Bos
And those are just yeah.
Wes Bos
So those are just functions that take in a wide type.
Wes Bos
In in my case, it will take in a response or a success or failure. Mhmm. And it will return, whether or not that thing is of a specific type. It will narrow it down for you. So in in my case, We have a function called is success. It takes in a success or failure, or in our case, we are gonna take in a response, and then the return type Of that. Or let's talk about the body of the function first. So the body of the function is all of the type cards we just talked about. Response that has own property or response that, message is equal to success or whatever the way whatever way you're going to try to figure out if something is of a specific type.
Wes Bos
Those are your type cards, but putting them into a function, it returns a boolean. True or false. But the difference here is that You are not returning a boolean response type in TypeScript. So you don't put colon boolean after the function definition. You put Colon response is success, meaning that you say the argument to this function is of a specific type. And what what that does is TypeScript. Of course, you return a Boolean, but TypeScript knows that this function is specifically returning whether or not something is of a type. And that is All the type predicate saying response is success.
Predicates return narrowed type
Wes Bos
And then you can go ahead and use that function, all throughout your your code base just as a nice, handy, reusable function.
Wes Bos
And and where I've run into this a lot is If you have an array of, types, you have an array of responses and you want to filter only for successful responses. You wanna take all the failures out. Mhmm.
Wes Bos
Simply running a dot filter and checking for a successful value Bro. It's not enough. Yeah. Which is odd. Yeah.
Wes Bos
You would think TypeScript would would narrow the type for you there, but it doesn't.
Wes Bos
So you have to explicitly define a type predicate with one of these is properties. You can do them in in line as well. You don't have to make a separate function.
Wes Bos
And then then you can if you hover over it before the function, it will say success or failure. You hover over it after
Scott Tolinski
that Predicate. It will say success. I've never used this before ever.
Scott Tolinski
And it sounds super health, healthy. It sounds Super handy.
Scott Tolinski
Sounds very healthy.
Wes Bos
No. It sounds super, yeah, super handy. It it's one of those things where you don't necessarily Need it until you need it. So, like, I was the only reason I had is a couple I don't know. 6 months ago, I was trying to filter a list of promise All settled results Mhmm. Which promise all settled will return rejected promises and resolved promises. And I was trying to filter them just for the resolved promises.
Wes Bos
And after the filter, I was like, Okay, these are all successful. I could go ahead and access the properties on successful types. And it's like, it's possibly an error or a failure. I was like, what? And then I was like, okay. It you have to explicitly use a predicate here because I I bet TypeScript will fix this at some point because it should be able to infer that TypeGuard for you. Right? But it doesn't. Right? Yeah.
Scott Tolinski
There's tons. There's I think some things around type guards explicitly are like times where I feel like, is it me Or is it TypeScript in this regard? Like, am I doing something that is inherently unsafe here, and am I Am I not, like, recognizing that, or is TypeScript just not able to infer what exactly things are? And and I I often lean on it being my fault because, you know, I'm not all knowing or anything. But there are times where I'm like Yeah. It. It feels like you could do a little bit more for me here, TypeScript. That'd be great. Yeah. It's it's funny that
Wes Bos
whenever I run into those, I'm like, I'm probably not covering some use case here. And specifically with that hoisting example that I gave earlier, I was like, oh, this is a bug in TypeScript For sure. And I looked it up, and I found all these I have all these GitHub threads, on GitHub being, like, there's a bug in TypeScript, And all of them are like, uh-uh. It's hoisted.
Wes Bos
There's a possibility.
Wes Bos
But, like, also, like, that with that hoisted example, like, couldn't Figure out if you are using it before it's defined. You know, like, it should be able to do that. But I don't know. Yeah.
Scott Tolinski
It. It it is so interesting to me because, like yeah. A lot of these I I do often Yeah. Your first inclination is, like, oh, yeah. There's something wrong with TypeScript here. And it is funny that, you know, it is almost not. It's almost never that way. Yeah. Not to say it won't be, but, you know, it's usually you. Yeah. It's usually you.
Scott Tolinski
It's not TypeScript. It's you. That's a T shirt.
Wes Bos
That that's a great I love that. Cool. Alright. That's, that is type narrowing, type guards, and type predicates in TypeScript. Hopefully, you Understand a thing or two about it.
Wes Bos
We'll catch you on Wednesday.
Wes Bos
Sick. Peace.
Wes Bos
Peace.
Scott Tolinski
Head on over to syntax.fm for a full archive of all of our shows. And don't forget to subscribe in your podcast player Or drop a review if you like this show.