Type Transformations Workshop (9 exercises)
solution

Use infer with Generics to Extract Types from Arguments

Before we get to the solution, let's take a look at another example.

Here Example2 is calling GetPoint on MyComplexInterface, and we're trying to return the 4:

For a first pass at a solution for GetPoint, we call MyComplexInterface and pass in any for each of the slots:

If we change Example2 to call GetPoint with a single random argument, we'll end up with never because of the branching logic.

Use infer to Extract From a Slot

So what we need to do is somehow inside here is use an infer to extract out one of the slots.

The Not-Ideal Solution

One option to make the test pass would be to update GetPoint to use ReturnType<T["getPoint"]>:

The issue with this solution is that it ties the conditional type to the internal structure of MyComplexInterface. This pattern might not be something that you always wanna do.

The Better Solution

Instead of tying into the structure of the interface, we should instead just look at its public declaration because it is less likely to change.

We can add infer to any of the slots in the declaration (where "slot" is anything between angle brackets). In this case, all of the slots above are any.

Because Point was the fourth argument, we can replace the corresponding slot with TPoint then return it for the matching conditional branch:

This approach gives us all of the benefits of being able to extract out all the members of the type arguments without needing to dive deep into the element itself to understand its structure.

You could add infer for the other slots, but in this case GetPoint is only interested in TPoint.

Being able to extract type arguments to another type helper is another really cool feature of infer.

Transcript

[0:00] [0: 00] Let's take a look at the solution here. I've got an Example2 here, which is going to call GetPoint on MyComplexInterface, and we're trying to return this 4 here. As you'll see, it's currently hovering over any.
[0:15] [0: 16] What's going on here is that, inside the GetPoint function itself, what we're doing is saying T extends MyComplexInterface, and we're passing any into each of the slots. If we don't pass any of these, then it's going to yell at us because it requires four type arguments.

[0:29] [0: 30] What we can do is we now know that T extends that thing. If I try to pass in something random like 1-2-blah-blah-blah, then I'm going to get never because it's going to hit that branch there instead of this branch. What we need to do is somehow, inside here, use an infer to extract out one of the slots.

[0:51] [0: 52] One thing we could do is say T, let's say getPoint, return type getPoint. That makes our tests pass. We end up with the 4 here that we're passing in. If I change that to 12, then we get 12, but it sort of ties the conditional type to the internal structure of this interface here, which might not be something that we always want to do.

[1:16] [1: 16] We want to look at the public declaration of that interface, which is the type arguments, which are pretty unlikely to change, or less likely to change let's say. Inside here, what we can do is let's say any for now. We can add an infer to any one of these slots here. Let's say we infer TPoint here. We can then just return TPoint.

[1:42] [1: 43] Now, you get all the benefits of being able to extract out all the members of the type arguments without needing to dive deep into the element itself and understand its structure. We can even do more of this. We can say infer -- what's that one -- if infect TEvent, infer TContext, and then infer T whatever. What you end up with is a bunch of different infers.

[2:12] [2: 12] This has no behavioral difference. I'm just showing you that you can infer multiple slots if you want to. We can return the TContext if we want to or the TEvent or something. Having them as any is also fine because all we really care about in this case is extracting the TPoint. This is another really cool use case of infer because you can just extract out the type arguments to another type helper.