Challenges 4 exercises

Use Recursion and Mapped Types to Create a Type Helper

Here’s what the solution looks like:

type DeepPartial<T> = T extends Array<infer U>
? Array<DeepPartial<U>>
: { [K in keyof T]?: DeepPartial<T[K]> };

As mentioned in the exercise introduction, we can recursively reference DeepPartial inside of itself.

To explain how the solution

Loading solution


0:00 Let's take a look at the solution here. This solution is kind of interesting. What you can see is, first of all, DeepPartial itself is declared up there, and then it's used inside its declaration. Similar to JavaScript functions, we can actually reuse the type helper inside itself.

0:20 To show you how this is working, I'm actually going to delete this first aspect of the conditional check. DeepPartial here. What's going on is we're saying K in keyof T, assuming that this is an object, within calling DeepPartial on the keyed or the value of the object that we're iterating over, which is interesting.

0:43 If we were to remove this little modifier then, then what we'd end up with is we're just creating the object again. That's kind of intriguing. All this is doing then is it's basically just making this optional in here. We end up with A string undefined, B string undefined, C string undefined.

1:04 If we say const results is a result and let's say...Let's actually go down into the depths of this, because this does work recursively. What we're saying then is I don't need to provide a D inside here. I don't need to do any of that. I've got G inside here, which is the array too. Even inside the array, this actually works pretty well too.

1:27 We've got the blah, blah, blah. I don't need to provide all the aspects to the elements of the array too, so why is this now yelling at me? Surely, I'm done here, right? Well, there's one little wrinkle inside here, which is we don't want to allow passing inside G. We don't want to allow passing undefined here, which this type lets me do. It's slightly imprecise here.

1:54 Actually, we need to handle the case when T is an array. If you think about a T, because we're doing this DeepPartial thing, we basically go, OK, first of all, we call it on the outside object. Then we call it on TK. K is here. This is going to be the thing that we're accessing. We get to call on all of the values.

2:14 On this C, then on D obviously, then E. Then it gets called on G too. We need to handle the case where G is this. You shouldn't be able to pass undefined to a member of an array here. Let's do that. We're going to say T extends any array. In fact, I'm going to use this syntax.

2:38 Let's say an array of any. If it does, then what we're going to do is we're going to say array, and let's say...I think I might be able to pass T here, but that's imprecise because T actually represents the array here. What we want to be doing is we want to extract the thing that's inside the array and then pass that back to it.

3:04 In fact, maybe I can just return. Oh no, right, because what I want to be doing is extracting DeepPartial front. What we're going to do is say infer U inside here, and then say array is DeepPartial U.

3:22 What's going on there is we're saying if we get to G and we grab G, and we understand that it's an array, then we're going to infer the member of the array, the thing that's inside the array, and then return a new array with the DeepPartial inside it.

3:41 This is actually going to not allow undefined to be passed here, because the only thing we should be able to pass is an object with H and I inside it. Wow, intense.

3:54 We could go even further here if we had some other...I'm feeling in the back of my mind, in the depths of my heart that maybe there are some cases that this doesn't handle. For this example, this is perfectly fine because we're handling the arrays properly. We're handling the objects properly.

4:11 When we get to a value like, let's say, T is a string or a number, then technically, you can say K in keyof T except key of string is sort of never, basically. This is like a noop when it reaches a string, or a number, or a Boolean.

4:30 For our use case here, it does seem to be working perfectly fine. You now can't pass in undefined into this area, which is exactly what we wanted. I think we're pretty much done here.

4:44 This is extremely hardcore like, bit of TypeScript here. This is where TypeScript can get really, really tough, which is where you have an extremely dense set of syntax, which all is like pressures and all belongs there with almost no space whatsoever, like can't delete anything, can't remove anything.

5:07 Hopefully, this gives you a good idea of the power recursive types, also the power of this array inference, and the power of this optional type here too.