Check out a free preview of the full TypeScript 5+ Fundamentals, v4 course
The "When to Use Generics" Lesson is part of the full, TypeScript 5+ Fundamentals, v4 course featured in this preview video. Here's what you'd learn in this lesson:
Mike introduces generics that allow for parameterizing types and creating more reusable types and provides an example of creating a dictionary from an array of objects. Solving this problem using generics, and how to define a type parameter and use it to create a more flexible and type-safe solution are also covered in this segment.
Transcript from the "When to Use Generics" Lesson
[00:00:00]
>> The last topic we're going to cover in this course is generics. This is really getting to the end of fundamentals and sort of tipping your toe into the intermediate level. And if you wanna go deeper than this, check out the intermediate TypeScript course. We pick up right where we leave off here and we start working with generics and talking about more interesting and powerful things that layer on top of it.
[00:00:32]
Generics let us parameterize types and it allows us to create more reusable types that can be expressed in terms of type parameters. So let's talk about a motivating use case because I don't wanna just throw abstract stuff at you and tell you that it works this particular way.
[00:00:54]
Let's ground this in a story. So I'm back to my contact information app. I wanna store phone numbers, right? Now I have an index signature here. So each in my dictionary of phone numbers, each value has a customer id, an area code and a num, this is a number, phone number.
[00:01:23]
And we can see that I can hang whatever I want off of this thing and they all have the same type. Here you go. All have the same type. I'm going to switch to my code actually. You can follow along on the website, but it's the same stuff.
[00:01:40]
So let's say had a list of these phone numbers here, right? And I want to create a dictionary of them. I have something like this and I want to end up with something like this. And I'll tell you where we're going to go here. I'm here. So I'm not trying to throw a crazy twist at you.
[00:02:07]
We're going to solve this problem specifically. And then we're going to widen it out, and then to put the generic solution that will allow us to go from a data structure like this. To one like this, in the general case, phone numbers, anything else. So, first off, we're going to create an interface representing phone info.
[00:02:30]
It's the same type of object that we were seeing in that index signature before. And what we want in the end, this is just the type of the function. We're kind of just stating what we're taking in as an argument, that, and what we're returning at the end of the function.
[00:02:49]
In fact, I can implement this really quickly. Done, all right, let's go home. No, we still have a lot of work to do. In particular, we need to provide this third thing. It's a function that we use to determine the ID of each object. So, if we look at this, we need to figure out this here, right?
[00:03:11]
Given one of these, we need to extract out, like, what's the key? I want the stored in the dictionary as. So I'm going to take in a function there because I want my caller to be able to provide me with that information. The caller gives me the list, the caller tells me how each item of the list should be treated, and I'll spit out the dictionary.
[00:03:40]
So the first thing we need in our function body here is to initialize a data structure of the right type. We can return it at the end. And we want to loop through every element of the array, right? So this is the list of phone numbers. For each of them, we're going to take this element, which is a PhoneInfo, right?
[00:04:09]
So it's just one of these, and we're going to get the key from it. The dictionary key. I'm going to invoke the callback that I'm provided with. Remember, it's the caller's responsibility to tell me, each member of the array, how do I obtain an ID from that member of the array?
[00:04:30]
And so it's going to generate an ID, it's going to take a phone info in, and it's going to return a string. So that's my dictionary key. Then I'm going to take my dictionary, and under the key, I'm going to store the element. Everyone following so far? Good.
[00:04:49]
Okay, and this is what we're going to get as the output. So, we would we would get this result out. And this will work. If we were to run this code, it would work. But, it's not very generalised, is it? There's phone info all over the place. What if we wanted this to work in a way where we could do an array of dates, or we could do an array of, I don't know payments, each of which has an ID something like that.
[00:05:24]
And we want the ability to say here's a list, transform it into a dictionary for me. So if we want to generalize this, we're going to have to change the function signature a little bit. And let's start by making it more flexible. So to make it more flexible, I need to be able to return a list of anything.
[00:05:52]
I could do this. Great, now it works with whatever I need it to work with. I mean, is anyone happy with this? Are these some great types that we've written out here? Not really, any is all over the place. We have flexibility now, we needed to sort of broaden what we tolerate as inputs, but we've lost all that great type information.
[00:06:31]
So what we need to do is define a type parameter. I'll uncomment these so you can see the little clues that I'm reading as I go along. I already know how to do this one here, but the we're going to define a type parameter and we're going to start writing it this way.
[00:06:50]
So this, you can almost think of this as another arguments list for types. So, I could do something like that. It's just like an arguments list for a function. In case you're wondering, you might see T all over the place for generics. You can trace that back to, I think they're called, I've gotta think back to my C++ days, template classes, T for template classes.
[00:07:22]
So it's just a cargo call, a convention that people stick to. You can make this whatever you want. I can make as many letters, whatever you want. The conventional, its capital, it's helps disambiguate between function arguments, which should start with lowercase. Okay, so we have this type T and we're going to say we take in an array of Ts.
[00:07:50]
And we're going to say, our ID generation function takes in a t and returns a string. And ultimately what our function's going to return is a dictionary of Ts. Sorry, this isn't in any array. This should be just a t. All right, T for everyone. So, okay, now, this is starting to look interesting.
[00:08:18]
Let's see what our function invocation looks like. Okay, that's something. Notice how this is between the angle brackets there. Our type param has been inferred, meaning we passed in this phone list thing. And we knew we're taking in an array of Ts, so you can think of it as the type param is figured out for you behind the scenes by TypeScript based on what you're passing into the function.
[00:08:49]
So you don't often need to explicitly state your type params. Sometimes you do in cases where it can't be inferred. But in a lot of cases, you might be benefiting from generics from type parameters without even knowing it. That's kind of the best type of generics were like.
[00:09:09]
Engaging with them. It's simple. You don't even know they're helping you. But you get all this great type information coming out the other end. Look at our result. It's a dictionary of these objects here of our phone info objects. And we get type checking. In our little callback here, because we've taken an argument of type T, and here it is.
[00:09:33]
So if we were to look at the type of that argument, look at that. Here it is. Here's the arg, here's the return value. So we get type checking all the way through. This is a type param, that's what's working for us here. So let's take a little bit of a step back and look at a simpler example, just so we can wrap our heads around this.
[00:09:59]
Let's define a much simpler typed function, and we'll call this wrapInArray. So this function just takes in an argument and returns it in a tuple of size 1. That's all it does. This is it. That's the whole story. So if we say wrapInArray(3), well, look, we've passed in a number and it's figured out what t should be.
[00:10:32]
And it returns this, a tuple of size one with a number in it. If we do the same thing with date, it figures that out. It's like auto setting T for us behind the scenes. We do it for RegExp look at that. So we get this flexibility, the same flexibility we got with any, but we're not losing specificity.
[00:10:58]
We're not losing all of that type information by calling this function and getting a bunch of anys coming out. We get to keep that. Let's see, can I do that wrapInArray? I would've to do this, right? It's all in there. There's my exec method, which is on RegExp.
[00:11:22]
So all of the type information is flowing through.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops