Check out a free preview of the full Intermediate TypeScript, v2 course:
The "Record & Pick" Lesson is part of the full, Intermediate TypeScript, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Mike explains that mapped types are similar to array.map for types, where you can iterate over the names of fields in one type and produce another type that is transformed in some way. How to create a mapped type called "record" that specifies a set of keys for a type and the built-in type "pick", which allows you to generate a new object type by specifying the names of some fields on a value type is also covered in this segment.

Get Unlimited Access Now

Transcript from the "Record & Pick" Lesson

[00:00:00]
>> As we think about the next topic, which is mapped types, I want you to recall that we studied index signatures and index signatures look like this, right? Like a dictionary type, right? We're saying you can gimme any key, and it could be, you could put a value of a certain type under any key.

[00:00:26] So this is kind of our starting point for mapped types. The way I want you to think about mapped types is it's almost like array.map for types. Meaning, we're trying to almost like iterate over the names of fields in one type and produce another type that is transformed in some way.

[00:00:57] So, let's dig into that. So, our example here, we have fruit, we have a dictionary of fruit, we're saying, a fruit could be there or it could not be there. Here's our fruit catalog, it's dictionary of fruits, and here's apple, could be fruit or undefined, right? Now, the important thing to note is apple's not there, right?

[00:01:19] So that's why it's saying it might be undefined. So let's build our first mapped type based on that example. Okay, this kind of looks like an index signature, and I'll leave line 9 in our field of view here so that we can see the difference. This is saying any key that's a string, that's an index signature.

[00:01:44] You could put whatever you like there. That's what makes a dictionary a dictionary. Here we have some different syntax. We're saying fruit key in apple or cherry. This is a type parameter, it's another kinda type parameter that just sort of pops up in the type expression itself. It's not over here, it's popping up in the type expression.

[00:02:13] So what we're saying is FruitKey is a property name in the set of apple and cherry, and then you get a fruit. And so here you can see that, we can have a cherry, we can have an apple, but we can't have a pineapple only. Effectively, we've said only known properties or only a property of particular names are present on this thing.

[00:02:41] It is no longer a dictionary, we call this a record. Dictionaries have arbitrary keys, records have specific keys. Another important thing to realize here is, and this is true of index signatures as well, valid property names are not just strings. You can have symbols as property names and you can have numbers as property names.

[00:03:10] So, let's look at record. Let's start with a type that says any possible key. And boy, this is just what I said just now, keyof any. That's like, what are the valid things that could be keyof anything? String or number or symbol? Keyof can only return those kinds of things, only evaluate to those kinds of things.

[00:03:35] All right, so let's look at this, we can say, okay, my record is generic over a type parameter K, K extends keyof any, then I have a value. The value's not really interesting here. Just as in the case with dictionary, right, value ends up being the value type, right?

[00:03:58] Like see, T, just ends up going over here. It just sort of passes straight through as the value type. So maybe ignore the V for now, but let's study this piece by piece. What we're saying is, K is a string, a number, or a symbol. That's the minimum requirement.

[00:04:20] That's what we're saying with our type parameter constraint here. Over here, here's our k, right? Minimum requirement, a string or number or symbol. Now we're saying, for each key in this type, for each, it's almost like a for each, here's the value I want you to show me.

[00:04:46] So that's a good starting point. Let's look at what we can do with that. Well, first, let me do the big reveal here, this is record. So there is a, It's a built-in TypeScript type, here it is. And if you see that I, if I were to just take key, if I were to turn that into this letter p, effectively, that's what we've got.

[00:05:17] Let's put this on a new line so we can see it formatted the same way. It doesn't like that? Why doesn't it? Because I have this. I'm missing the open brace, that would explain it. So there you go. It's just like a different name of type parameter, but this is record.

[00:05:39] So, let's look at what we can create with this. My, Do I have a fruit up there? Yeah, I have a fruit. So we could say it's a record. You can make these even first or second. Get rid of this little thing. If we hover over this, Or if we do this, can we get it to expand that out?

[00:06:28] Now we can't, we have to rely on autocomplete here. There's first, there's second, they're both there. So effectively, we're not saying any old place you can store a fruit, you have a specific set of keys. So let's combine this with an indexed access type. And I should point out to you that, what is the mapped type here?

[00:06:51] It's this. This is effectively, like, array.map. We're saying here's the array, all of the possible keys. Sorry, I got it wrong. This is the array of all the possible keys. This is almost like your I, right? It's changing with each turn of the mapped type, and then here's the value.

[00:07:20] So we've got key in and then a couple names of properties, and look what we can do with this. Indexed access types allow us to retrieve the type of a field on an object type. So here, it's almost like we're saying. Look at this, y is of type document.

[00:07:52] Give me the type of your document field window? Now note that, we're able to use this type param, which represents kind of the key that's changing with each iteration of the loop. If you wanna think about it, like array.map, we're allowed to use this over here in terms of defining the value type.

[00:08:17] This is pretty powerful. So what we've got here is, we're letting ourselves get a subset of a type by specifying the property names. And we can generalize that a little bit. And we could say, all right, I want the caller to be able to to decide which property names they're asking for.

[00:08:38] So we could say, I want to get a part of a window. We'll just make that two to not collide with above, pick window properties. And we've got document navigator, set time out. And sure enough, if we look at this, there's document navigator and set time out. We've plucked those off and we have a new object type that represents some subset of these properties.

[00:08:57] We can make this a little more generic by saying, hey, let's not just make this about getting things off of window. What if we introduce one more type param? We'll call this value type. And that's what we're gonna iterate over here, not window, but a type param. So you're gonna pass pick properties a value type and then you have a type parameter called keys.

[00:09:24] And keys must be a subset of keyof value type. Which means, we can't just pick, and I had the same similar constraint up here, right? If I do this, It's not gonna be happy with me, foo is not assignable to type keyof window. I can only specify the names of properties that are on window.

[00:09:49] Same below, for a given value type I pass in, I can only specify known properties on that value type, whoops. So I pass in window, I pass in property names that are on window, I'll prove the constraint still works, And what do I get? Exactly the same thing.

[00:10:14] And what we've arrived at is pick. This is another utility type, Pick. Given a value type and the names of some fields on that value type, generate a new object type that almost plucks the key type pairs that were named.