Intermediate TypeScript

Intermediate TypeScript Advanced Mapped Types

Learning Paths:
Check out a free preview of the full Intermediate TypeScript course:
The "Advanced Mapped Types" Lesson is part of the full, Intermediate TypeScript course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses using mapped types with indexed access types, demonstrates generalizing using type params, and allowing the type to work on anything and not just Window. A student's question regarding if mapped types extend from dictionary is also covered in this segment.

Get Unlimited Access Now

Transcript from the "Advanced Mapped Types" Lesson

>> Now let's combine this knowledge with what we just covered in the previous chapter, and that is an indexed access type. So what I've done here, and I apologize that the tooltip is kind of adding a big gap in the code, but this is just one type here, right?

[00:00:23] What we're doing here is we wanna grab a portion of the window API as in the global that's available in a browser. And when do I do this? Well, especially if I'm writing code and I expect to make my tests nice and easy through dependency injection, I kinda want to limit myself to accessing only certain things on Window.

[00:00:54] So that I make sure that if I have to stub them in tests, or maybe if this is a single page app that I have to server render, well, there in the node world I might have to stub like polyfill some Windowish things so that the thing renders properly.

[00:01:12] But I wanna give myself sort of a explicit boundary so I know whenever I cross it. I can show you after we go through this a more real world example involving Chrome extensions and their Chrome extension API's. So great. So we have, we gonna call this part of Window and what we want is to grab only, we want something that looks like Window but it only has a couple things that can be found.

[00:01:46] So we're saying we're looping over these three things. So it's sort of almost like four each of these three, key is gonna be these sort of one at a time. And now we're using an indexed access type, right? So key is first gonna be document. So we're using Window[document], and we're gonna get this document type.

[00:02:11] And then the same thing for navigator, and then the same thing for set timeout, right? Here's the callback, and then here's the timeout number, and then here are the the arguments that are passed to the function. People, this is a rarely used aspect of set timeout. But ultimately, we get a subset of Windows, we get a portion of it, only these keys that we're interested in.

[00:02:42] So again we're gonna make this a little bit more generalized by pulling hard coded things out into typeparams. So the first thing we'll pull out is, we wanna pull out the keys, right? So that's this list of things here. So now we've got like Key in Keys, and that's a typeparam up here.

[00:03:05] And note that we're saying, Keys extends Key of window. Why do I want this, well pop out to the playground just so we can see it for ourselves. What if I did this? I would wanna be told, sorry, it's a pipe. I would wanna be told that zzz it's not a thing that could be found on Window, it's not found on Key of Window, Right?

[00:03:36] A deliberate spelling error. If I took this away. Well the error would pop up in a different place. But now I'm being told hey, hey, hey, you can't just use Key as an index access type here, how do I know that that stuff's gonna be found on Window?

[00:03:56] You could say hey it's a string, but so the first error is Keys, it could be a promise it could be a function, there's no constraint on Keys. I can't use that in a match type cuz can only use things that are like property keys, right? This is a core constraint of map types.

[00:04:15] It's about a property Key and looping over them. So even if I solve that by saying, It's a string, now this piece is gonna say, well, how do I know that these are things that I can get off of Window. It could be an arbitrary string. So that's why we have this in place just kinda makes everything work.

[00:04:38] It's not only a valid property key but it's the subset of all allowable property keys in JavaScript that will work with Window. And now, theme coming back, right? The error is popping up at the location where I apply my fix because I have misspelled this, and now things start to pass.

[00:05:00] So that's why you can see kinda how if we relax this, things will start to get angry in various places. This has to be a valid property key and hen this has to be a valid property key for Window. But we can get the same result out now, right?

[00:05:26] So we had part of Window, this was hard coded for these three properties. So we made something here called PickWindowProperties, that's the more generalized thing, and then we can leverage that. So this is the general solution up here. We can get our specific solution back by saying, I wanna pick things off of Window, and here are the names of the properties that I wish to pick.

[00:05:53] Let's generalize it one step further by abstracting Window away. So we're gonna get rid of this and pull it out to a typeparam. So what we've done here, instead of calling it pickWindowProperties, we're just calling it PickProperties. And just as before we have these Keys that come in, I've actually moved it to the second type parameter, up here was the first, right?

[00:06:18] I mean, it was the only one but it's in the second position now. So we have a valueType and then we have Keys, where these are Keys that are found on this valueType. And I'm using this map type here. So previously we were seeing what Window[Key], and now we're seeing ValueType[Key].

[00:06:42] And we're still able to get our specific result here. Because we're parsing in Window, this is the thing we're plucking properties off of, and these are the names of the things that we're kinda peeling off. Let's open this in the playground. So I don't wanna talk about why I ordered these typeparams in this way.

[00:07:11] If I tried to reverse them, I might run into a problem, actually does this work now? That would be pretty cool. Wow they fixed this. All right, well, you'll still see stuff written the other way and I can kinda explain the rationale behind it. Just poking at it making sure, something's not right here.

[00:07:39] Let's see, I need the comma and then I think it'll work. Yep, okay, so it seems like this has been relaxed. But previously, you kinda had to state upfront. If I wanted to use ValueType in an argument, we first had to state the typeparam has to exist first, and then we can refer to it as a constraint for another typeparam.

[00:08:07] But it appears that recently they've relaxed this, which is kind of cool. That would let us build this API signature the way that we want, and in this case, just in terms of how you could read this out loud, I kinda like like this better. So I want to pick the properties document, navigators, setTimeout, setInterval from Window.

[00:08:30] And I like that rather than saying I want to pick the following things from Window, here are the properties that I want. You have both options available to you now. All right, Tristin asks a good question, in fact you've asked it twice and you've been very patient. So mapped types do they extend from dictionary, and since mapped types are a subset of a dictionary that extends word is still strange to me.

[00:09:03] So I would encourage you to think about this in terms of set theory. So if you imagine all of the dictionaries that could be in the universe. So things like this, right? All of these with all the possible property keys and all the possible ValueTypes. Some of them, [LAUGH] Some of them will be full of stuff, some of them will only have a couple property Keys.

[00:09:41] When we talk about something that is more constrained than this, more specific than this, we would say that it extends from this. So when we talk about mapped types kind of extend from their dictionary equivalents, you can think of it as, Dictionaries are more flexible than mapped types.

[00:10:09] So if I had, Or well, say this is a dictionary, Of dates, So it starts at empty. And then this will be, A index signature. And this is gonna require that I do a new date. Start of week is a new date. And I'll get rid of this little side panel here.

[00:11:04] Okay, so just looking at this code here, I would say that this dictionary is more flexible and more general than this type here, why? This has no opinion about which Keys dates are stored under. This one does have an opinion. So if I change these to let declarations, This is gonna be a problem.

[00:11:36] Because we can't, when we say I have a slot that's designed for a record, what guarantee do we have that end of week and start of week are here? We don't have that guarantee. Whereas in the other direction, Totally fine, why? The dictionary is ready to handle start of week, end of week, start of year, end of year, whatever you want.

[00:12:03] So that's, we see a Key insight here that records, these records are more specific than dictionaries. And when we talk about this extends word that's used in type parameter constraints, and in these conditional type conditions, the meaning of extends is, Something like this. We're saying is T, are all values that we could possibly have for T, to all of those satisfy what is described by date.

[00:12:48] And date could have many others that don't satisfy T, but does everything in the set that could be a possible T? Does all of that fit into date? So really we're saying, is T a specialized flavor of date? Is it a more specific version of date? It's a really tricky conceptual thing to get over, so I would encourage you if you're still having trouble with this, please ask more questions.

[00:13:16] Please keep kind of poking at this because it's one of these big lightbulbs that when it turns on, things become much more clear pretty broadly across the language. So now that we've generalized this let's look at what we have. We have a mapped type where we take two typeparams here, right?

[00:13:35] So we take a value that we're plucking things off of this previously was Window. And then we have some union type potentially, that is one or more Keys that are found on this ValueType. And we're iterating over those Keys, and we're plucking the values off and this is what we get.