Handling Default Arguments with Function Overloads
First let's dive into why this error is happening in the original implementation:
The function results in an error because
getObjectValue can be called with a variety of different inputs.
0:00 Let's dive into why this error is happening. The reason it's happening is because getObjValue here, it could be called with a bunch of different things. It could be called with a. It could be called with b. It could be called with c. It could also be called with a union type of those members. It could be called with a or b like this. It could be called with b or c, for instance.
0:21 Defaulting it to A doesn't give TypeScript enough to work with in terms of inferring stuff. It doesn't quite make the leap that if you don't pass anything, it wants you to grab a in this slot here. In fact, it still infers a, b, or c. You can think about this. I gave you a clue in the problem set up as having two different call signatures.
0:49 You've got the call signature where you don't pass in arguments and you've got the call signature where you do pass in arguments. This makes me think of function overload. If we extract this out into a function overload, so pull that over here, pull that over here, copy this over here.
1:08 We obviously can't do any runtime stuff in a function overload. We're going to say key TKey equals, the thing we're getting back is typeof Obj and then TKey. This means then I can remove this for now. We're now handling the first case in our function overloads. We've got our 1 here. We've got our 2, got our 3. We also need this oneByDefault.
1:36 Now we can pass this up here. We don't need a generic up here. We can just say there's no key being passed, no inference needed. The thing that we're going to be getting back is the 1, or we can do typeof Obj and then a, for instance. This means that we don't need to manually specify it here.
1:58 Now we're getting an error here because the overload signature is not compatible with its implementation signature. What we can do here is we don't need this generic anymore, because all the inference is happening up here.
2:12 All we need to do inside of the implementation signature is make sure that this is compatible with these two. We can remove ObjKey here and make this optional. We can then say obj.key or a. Or, in fact, we can move the default value up into here, too. We can say key equals a.
2:33 Now this all works. Let's break this down just a little more. We can see that oneByDefault is hitting the first. I did just a little cheeky go-to definition there to see which one it hits. It hits this one because we're not passing anything to it. We go here on the second one, we can see that a go-to definition hits the second overload.
2:55 If I pass something to this, then I'm going to get a, b, or c locked in. I'm also getting my oneByDefault back. This means that there's only generic inference happening on one of the overloads, on this one. This one doesn't need any generic inference because it's just working off the function overload, working off that call signature.
3:15 This one doesn't need generic inference because it's pretty loose. The generic inference is attached to one of the function overloads, which is outward-facing. This is basically just implementation stuff.
3:32 There's quite a lot in here. It's a complicated solution for what's a really complicated problem. TypeScript seems really, really happy with this. It's fairly easy to read. It means that at least you have one solution when you encounter that horrible, horrible error.