The Inference Mystery Solved
The ParentNode
interface inside of TypeScript's ,lib.dom.d.ts
, file is where querySelector
is defined.
You'll notice that it has several overloads:
/** Returns t
Transcript
0:00 Ready to solve the mystery? Let's go. What I'm going to do here is I'm going to do a Go to Definition to find out why the types of querySelector are being so weird. We could go a lot of different places here, but I want you to be aware that where you do your Go to Definition really affects where you end up.
0:20 If I do a Go to Definition on Document, I'm going to end up in no man's land here. I don't want to think about the whole document. That's not what I'm analyzing. I just want to to analyze the querySelector, so I'm going to do a Go to Definition there. We end up in an interface that looks like interface ParentNode extends Node, and we get querySelector on here.
0:44 This is an interface. I don't want you to worry too much about what all of these different interfaces are. We may cover that in some bonus material, or some material somewhere. We have these functions here called querySelector, and these are basically, if we look at the whole thing zoomed out, methods on the interface here.
1:06 These methods on the interface, you can see that they're overloaded. If I were to create my own interface here, so interface MattInterface. Then let me zoom that in. Then I can actually have, a is a function, for instance. Let's say a, and a returns one, but if I call a with a "wow," then it's going to return two, or rather I should do the syntax properly, shouldn't I?
1:36 Let's call this param "wow," and returns two. You can see here that MattInterface, imagine if I have a const example, which is like this. As MattInterface, can we pull that to implement it now? Example.a, if I call it, then it's going to return one, but if I pass it a "wow," then it's going to return two instead.
2:00 This method lets you declare different overloads on interfaces. We understand what's going on, now let's check it out. We can now look at each of the querySelectors here because there's three overloads. We need to work out which one that we're triggering. We can do that by saying querySelector ("div").
2:22 If we do a Go to Definition on it here -- this is inside VS Code -- then you get taken to the exact overload that's being hit here. We can see that this one hits this line. This next one, the span, hits the same one. Then this one, which one does this hit? This one hits the third line.
2:47 What's going on here? We can see the querySelector here. We have a K extends keyof HTMLElementTagNameMap, and then the selector is being passed in there. HTMLTagNameMap, if I go to it, you can see that it has all of these different elements on it mapped to the exact tag that they're in.
3:11 If I choose these, then I should be able to actually get autocomplete here. A base ends up as a HTMLBaseElement. An area ends up as a HTMLAreaElement. That's really, really cool. If I get one of those, then I'm basically triggering, if I jump back to querySelector, this first element here, or this first overload.
3:36 If I choose the name of an SVGElement, for instance animateMotion, then I'm going to trigger the second overload. I'm going to end up with an SVGAnimateMotionElement there. You can see there, when I do a Go to Definition on it, then I end up on the second overload.
3:53 If I don't pass in anything that hits either of those keys, either hits keyof SVGElementTagNameMap or keyof HTMLElementTagNameMap, then I end up on this overload. That's what's happening here, is I'm getting a blah-blah-blah-blah-blah. I could pass in anything, and I'm going to end up with a generic element.
4:14 That's why divElement2 is not of type HTMLDivElement. It's because querySelector here isn't smart enough to know that if I start my thing with a div here, like div.whatever, I'm going to end up with a div at the end there. You notice, though, that it does take a generic, it does take a type argument.
4:34 It has an E extends Element, which defaults to Element. What this means is we can pass it HTMLDivElement here, meaning that we end up with, if I change the span back to what it was, something nice. Now what's happening is that it's returning HTMLDivElement because we're manually passing in that E in there.
5:01 What's the lesson here? Wow, there's so much to dive into here. This huge file, this is lib.dom.d.ts, this is full of APIs that you will be using in your projects if you're doing a web-based project. If you're doing a Node project, then you're going to be using types/node, which is possibly even more complex than this.
5:25 Knowing how to read this stuff, you don't need to know every line of it, but knowing how to read and diagnose these problems is really, really important. I'm going to be looking at that in future exercises in this core volume.
5:39 For now, knowing that these function overloads are available on interfaces, and knowing that you can diagnose them, should give you a real sense of power and give you a sense that, "OK, I can dive into this stuff and understand what's happening."
5:55 For now, what we've got here is different generic signatures based on different overloads, and this should give you a good sense of confidence when you're approaching this stuff and fixing these type of errors in the wild.