Intermediate TypeScript, v2

Intermediate TypeScript, v2 Inference with Conditional Types

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

Mike explains how to use the "infer" keyword to extract a specific type from a larger type within a condition. A demonstration of how to use inference to extract the type of the first argument of a function is provided in this segment.

Get Unlimited Access Now

Transcript from the "Inference with Conditional Types" Lesson

[00:00:00]
>> The next topic we're going to discuss is inference with conditional types. So conditional types aren't just about checking to see whether a given thing is a subtype of another thing. We can use the infer keyword to extract something out of a type within a condition, which we then can use separately.

[00:00:26] And when I talk about extracting something out of a type, I don't necessarily mean the kind of thing that we looked at with extract and exclude. But sometimes this is extracting a type per out of something, essentially. We're gonna look at a motivating use case. In this 08-infer folder, within our course notes, you can see that we have two files.

[00:00:52] One of them we're calling fruit market. You can see we've got apple varieties, orange varieties. We've got a list of possible allergies. We've got some type that represents degree of ripeness. We have a type representing a quality range. So there's there's a lot going on in here we've got fruit order item, we've got fruit order, fruit order preferences.

[00:01:15] And then finally there's something at the bottom that we export so outside the model, sorry outside this module, this is the only thing that we'd be able to see, it's create order. And it takes as an argument, this fruit order preferences thing. Now, we don't need to understand this in detail except in so far as like, you see that there are a lot of types referenced here that are that you only have access to by these names within the module.

[00:01:43] So let's go to our index.ts file, so here's our create order function takes fruit order preferences but there's no way for us to get that type right now. It's the first argument of that function. So what we're gonna do is use a conditional type to sort of tease out, that first argument.

[00:02:05] And what we hope to do here is get prefs like, if we've defined get first arg correctly. This instead of being of type any, it should turn into type fruit order preferences, right. Get first arg is going to do that for us. It's gonna take the type of the function, create order, and it's going to give us the type of its first argument.

[00:02:36] So let's get started, first we need to introduce something called the infer keyword and I want to look at a simple example here. Before we get into function signatures and things that are practical, but make things a little bit more complicated in TypeScript promises, have a type param promises are generic over the type of value they resolve to.

[00:03:08] So if you have a promise, wow, copilot you're awesome. Promise that resolve 1 2 3, so this is a promise that resolves to a number. How might we take the type of a promise, like this? Promise string, how might we take that and end up with this becoming a string?

[00:03:39] Well, we have the answer in front of us and we're going to walk through it. So, here's the answer. We're going to do it step by step, on different lines. First, we can identify that this is a conditional type, right? All of this stuff is a type expression, and sorry the whole thing including P.

[00:04:02] This represents the condition and we have our extends keyword here. So we're saying P extends a promise like what's gonna happen with this [LAUGH] Infer thing, come on. P extends promise like, and we have this infer keyword and what this is going to do is, well first of all the type, the condition expression here it's going to evaluate as it always would, right.

[00:04:35] Like in terms of whether we're sent down this branch, in the case that the condition is satisfied, or this other branch, in the case that it's not satisfied. This infer keyword will have absolutely no effect on that, but what it lets us do within the condition is tease out a new type parameter T.

[00:04:52] Note that there's no T up here when we're defining unwrap promise. We can use this T in this branch specifically where we have found that the condition is met. So, what we're doing is we're saying we take in a promise, that's our P and if P extends promise-like over some generic type T.

[00:05:23] We're gonna grab that T, and we're going to return it or we're going to evaluate up to it. And here you can see we get a string. Here you can see we get an array containing a string array and a number array. Here we can see we get a number so that's how infer works.

[00:05:42] infer can only be used in this branch, it cannot be used in this one right that this is the branch down here that represents the condition not having been met. And of course you could assume there that like, well, it's not a promise and so of course we can't extract out the type that the promise resolves to.

[00:06:01] We've failed to meet the condition so that's why only in this branch you could use T. It's almost like a type parameter that's sort of declared on the fly and can be used in this part of the conditional type. It's a really powerful idea. So let's go back to our fruit market example.

[00:06:29] First, we're going to need a type for a one function argument. And because of the way arguments work in JavaScript, there's a little fishiness around being able to check for exactly one argument versus at least one argument. But we'll do our best here. So we're saying, all right, I have a type and I think I don't need this any here in fact, I know I don't.

[00:06:56] I have a type 1 arg function that is generic over A. Its first argument is A and its remaining args can be anything, and there can be any number of them. And it returns a void, which means I don't care what it returns it could return whatever. And I'm going to ignore it, you might say, like, wait a minute couldn't we have done this or this?

[00:07:21] Something that's not null, not undefined. I'm putting a type pram here for a reason. It lets me use infer, so here's get first arg and I think I have that defined above so I'll comment that out. This is why I have the any, it is just a default for the type param, like in the event T is not provided, you can assume it's an any.

[00:07:55] So we're saying get first arg, if T extends one arg function, then we'll accept it as a type param. Now, in terms of what that evaluates to, we have a conditional type as long as T extends one arc function, we'll emit a string array, otherwise we'll emit a never.

[00:08:20] I'm just doing this so that we can see if we can identify a one arg function, like if we see string ray we'll know that our condition works. I do this all the time to test conditional types. You're just sort of testing it to make sure the condition flips the way you want and this is to make up for the fact that like, this is not imperative code where we can put a debugger somewhere and we can see what's happening.

[00:08:41] We kind of have to set up a test case and see what the expression evaluates out to. So here's our function, it has at least one argument and t1 is a string array. So we know that this type checks, we know that at least the positive test case works, so let's let's modify this a little bit.

[00:09:06] What we want here is to say T extends a OneArgFn, and OneArgFn is generic over its first argument. Just use R there. Why is it yelling at me? I have not used R yet, so instead of returning a string array, let's return R. Sorry I put that in the wrong place.

[00:09:36] That instructor. Up here, there we go. Infer R, so we're saying OneArgFn. So do you match OneArgFn? And if you do, let's tease out the thing that a particular OneArgFn has for its type param. Look what we get, string. String is in fact the type of the first argument here so let's go back up to the top here and See how this works.

[00:10:20] So prefs, what's the type of prefs? Look at that, fruit order preferences, so we've managed to obtain this type. Fruit order preferences, despite it not being exported from the module but we had like access to a type that included fruit order preferences, and using infer we were able to pluck that out as the type of the first argument and use it.

[00:10:48] And now if we wanted to we could sort of stage, to stage some preferences and then send them into the create order function. The remaining type type error we're getting here it just proves that we are type checking against fruit order preferences, like we don't have apples, oranges prefers local produce like that's we're working with the tape.

[00:11:09] Now that was the goal.
>> So, and you can tell me if we're gonna do this later, but I see it says never. But if I change like foo to not have any params it comes back as unknown not never.
>> Comes back as unknown, I bet that's the one arc function type here.

[00:11:44] Yep, it's going to be, and let's prove it to ourselves like more directly here. Let me explain this a different way. It's hard to actually type check to find a function that has a specific arg while being flexible to a bunch of different args. And you can trace this back to kind of how you can do function.call and function.apply and all that with, remember we used, well.

[00:12:29] I remember a time when we used to use the arguments list, like literally the arguments keyword that is available in a function scope. Type checking here is not as good as I would hope it would be, but this is kind of how it works. We actually never, as long as you pass in a function of some sort.

[00:12:49] Like if we if we make this a number, this well first of all it's going to fail this type prime constraint but like let's let's get rid of that. Just so we can see what happens just purely based on the condition, it'll flip to never here. It's really that sort of the the number of arguments of the function are sort of slipping through

[00:13:11]
>> Got it, okay
>> An unknown would be like an acceptable thing to fall out because at least you can't mess with it, right, it's still a dead end
>> Thank you.
>> Its a good question.