Check out a free preview of the full Enterprise TypeScript course:
The "Types at Runtime" Lesson is part of the full, Enterprise TypeScript course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses type checking at runtime and introduces the concept of type guards. Performing full type checking at runtime can be expensive and slow down an application. Instead, using type guards to perform type checking at runtime can add type safety to API responses or other places in the application like a data layer.

Get Unlimited Access Now

Transcript from the "Types at Runtime" Lesson

[00:00:00]
>> The next topic we're gonna tackle is type checking at runtime. And what I mean by this is really just type guards. So performing full type checking at runtime is like, it's possible there exist projects that try to use TypeScript in order to do this. It is quite expensive, it's very expensive.

[00:00:21] If you've ever used React PropTypes and scaled that up to a very large app, it slows things down considerably because it's doing a lot of comparisons against all types of values. Anytime you pass through a component boundary, it's really having to do a substantial amount of extra work.

[00:00:41] So this is a piece of type checking we can do at runtime, and by that I mean a type guard is where compile-time type information joins runtime behavior that matches that type information. It's incredibly important that these be good. This is a place for imperfections in types or imperfections in checking can result in code looking like it's OK and having very mysterious behavior.

[00:01:15] So if you're gonna focus on correctness in one place, focus on it for type guards. So what we wanna do here is add some type checking to our data layer like our source/data folder. And we did a little bit, right? We found that some of these files here.

[00:01:46] We have a return type here, but effectively we're casting, right? We're saying here's the API call, it returns a promise any. Here's an any, we can state that we are returning a promise that resolves to an i channel. But are we really sure? And any works, it works in that it could be anything.

[00:02:15] TypeScript's trying to be flexible here. But I want us to introduce some real checking here as data enters our app to make sure that this is what we hope it is. And to start, what we're gonna do is create another file in our source folder called type-guards.ts. And we're gonna add a couple of guards in here.

[00:02:39] We're gonna first say. Isteam. And if you use GitHub Copilot, it's often pretty good at this, it's this is a terrible suggestion [LAUGH] it's giving me right now. It's literally just casting Iteam. Yeah, that's pretty awful. Here's what we're gonna do instead, we're first gonana say if type of arg equals object, and let's see.

[00:03:29] Let's look pull up what an Iteam looks like right here on the side, there it is, and. See this is a much better thing here. I still don't like the casting, but this is more along the lines of what we're trying to do here. Get rid of this.

[00:03:52] So if it's an object. And typeof arg.icon URL And I think we'll have to do, and. There we go, sorry, it's. Tough to talk and type and think at the same time. So I'm gonna just create a bunch of those and we're gonna do this for name and for channels.

[00:04:34] We'll do another check on channels here. So this will be name. This will be, sorry, this will be channels. And then see icon URL name, channels and Id. Add one more up here. And here we can change this to. IsArray(channels). We have to actually do the first one first, that's right.

[00:05:33] There we go, okay, so effectively this is like an exhaustive check for ITeam. Now we could do something a bit more here. We're gonna build that out but this is the pattern we kind of wanna follow here. So we could say return. And thanks to like early termination, anything that doesn't really match this it'll short-circuit as soon as it hit something that it's gonna break.

[00:06:05] So just to break this up so people can see it more easily. That's kind of the pattern, we're gonna do this just for one type here, and then we're gonna make an array, like a typed array check as well. You could do this, of course, for the other types, right?

[00:06:28] For ischannel, isteam, something like that. So now I'm gonna do export function is typed array. And we're gonna take an unknown array as our argument, and a wow, now TypeScript's getting a little more clever. So a guard which is a function that returns something like that. We're gonna flesh this out here, I still have to do stuff here.

[00:07:05] And then we're going to say, all right we now know that this is an array of some sort, and if we want this to be a typed array, we kind of need this to be a generic function. So we'd say isTypedArray is generic over the type of thing in the array.

[00:07:23] We take in an array of Ts, we return an indication that arg is in fact an array of T sorry, we would take an array of unknowns. Right, those are the things that we have to test. Here's what our guard looks like. So we're gonna be able to infer the thing we're checking for based on the guard and we can kind of get to work implementing here.

[00:07:53] There's something I haven't done yet, but I wanna leave it as a surprise. Well, and that is the implementation here. We check to see if it's an array, and then array.every, it determines whether all the members of an array satisfy the specified test. This wouldn't. I guess this would work but there's something we could do to make this even a little bit more rigorous.

[00:08:34] We can say, I don't just want you to pass me a function that returns a Boolean, I want you to pass me a function that's actually a type guard for type T. Of course, this is a subset of what we had there before, right, it's a special Boolean, a Boolean that has special meaning.

[00:09:02] So what this will let us do something like this. Just this is a demo we'll bring it back into the code in a more meaningful way. And see people like furiously cutting things down. Did we do something wrong here? Isteam is typed array, team array. That's interesting.
>> I think if you provided the right argument.

[00:09:58]
>> I have to, yeah, I don't get inference cuz this is not a type param. What if we did this to force it to infer based on the return type? Hey, there we go. So, [LAUGH] this is a really subtle piece of behavior here. I add this and as this function, it's going through, it's trying to figure out what T is.

[00:10:26] The first thing it's gonna hit is to test, and it'll say, hey, you gave me unknown. But if I type it like this, it won't be able to infer anything based on this. It'll just look at the return type of the type guard, not the Boolean, but the thing it is checking for.

[00:10:45] So that's what gets us this result. See, it's an Iteam array compared to this. It's an array of unknowns, cuz it was like, well, I will just use the argument you're taking in, and this is an unknown. That's a tricky one. Okay, now we actually, this is a really useful type guard.

[00:11:12] We can scan through an array, apply a type guard to each member. We have a nice concise syntax that we can use here. Let's go back, let's find a place where we can use this. So we can go into our data layer, maybe our teams.ts. And we've got to get all teams here, right?

[00:11:31] Makes an API call, returns a promise that resolves to something like to any. What we can do here is add a .then, remember applying type guards you are running code. We'll call this. Maybe teams or response data. If is typed array, pass in the response data. And what's the problem here?

[00:12:14] We're not always returning. Something like that, we didn't import this I suspect. So look at that response data going in, it's an any coming out. It's an array of iteams. Ultimately, what this whole chain ends up returning is a promise that resolves to an array of Iteams. And this, by the way, gives us a much better clue as to what we're caching here, like what's happening here.

[00:13:02] And so we know this is now a promise that resolves to an array of Iteams. So now this function, it actually lives up to what it's supposed to be, cuz we added a nice type guard here. All paths lead to returning an array of Iteams regardless of whether you get the cache hit or the cache miss.