Intermediate TypeScript, v2

Utility Types Using infer

Intermediate TypeScript, v2

Check out a free preview of the full Intermediate TypeScript, v2 course

The "Utility Types Using infer" 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 continues the exploration of utility types and specifically discusses the "Parameters" utility type. The "Parameters" utility type extracts the types of the arguments of a function and returns them as a tuple. Other utility types such as ReturnType, InstanceType, ThisParameterType, and OmitThisParameter are also discussed.


Transcript from the "Utility Types Using infer" Lesson

>> Let's continue our exploration of utility types. So, we'll talk about this one called parameters. This is actually a generalized version of what we just wrote, where we extract the types of the argument of a function. In this case, instead of giving us just the first argument, it's gonna give us a tupple of all the argument types.

So, as usual, I have my underscore here, it only exists so that we don't collide with the real parameters type. But take my word for it, this is the type, this is how it's described. And here I have written out in some more plain language what all of this means, piece by piece.

So first off, parameters takes in a type parameter T, which extends, meaning T must be a subtype of any kind of function that has any number of arguments, any return value, that's the minimum requirement. You cannot say parameters of a number or parameters of a string. So that's the type param constraint here, and this is the most flexible function type you can imagine, any number of arguments that are of any type, and it returns anything.

Okay, now let's look at what this evaluates out to. So we have a conditional type here, T must be a subtype of here's that any function type again, right? Remember when we see infer P with no constraint here, this does not have this infer S extend string. This is what a type an infer type parameter with a constraint looks like, and down there, we see nothing after infer P.

So this P could be anything. What we wanna do is tease out and extract this. P is a tuple representing the rest param of all the arguments of the function. And if we extract that out, it means the condition is met, and we emit P, that tuple, otherwise we emit never.

Here's another one, very, very similar. This one gives you the return type of a function. T extends any kind of function with any argument types, any return value. As long as T matches something that looks like a call signature, I don't really care about the argument types, it can be whatever.

But whenever it returns, give me that return type, and hold it for me in a type parameter R. And in the event that you successfully met the condition, and got that type parameter R, give it to me, otherwise return any. Here's some more. It's more of the same.

This is why I'm not having us go through building each one of these piece by piece. I'll just point out the differences here. Instance type, the only thing new here is this. It's not a call signature now, it's a construct signature. And this word abstract is here because TypeScript allows for something called abstract classes, you can think of them as half class half/interface where pieces of the class may not be filled in.

Abstract classes are not directly instantiatable, but they can have constructor signatures rather, and it's a subclass's responsibility to conform to that to make sure that they are type equivalent to that signature. And so you could think of this as like the most flexible type of class. Abstract classes will work with this as well as non-abstract classes, right?

This is sort of think of all classes or classes that are not abstract or a subtype of all the possible classes, some of which may be abstract. This is maximum flexibility here. We don't care about the arguments passed to the constructor, we don't care about what it instantiates here in terms of the type parameter constraint.

But look down in the condition, we've got something that looks very similar except we're grabbing what the constructor returns, which is gonna be that instance type. And then we return it, otherwise any. This looks so close to what we just did up here, it's just literally adding abstract new.

All right, and these two I'm gonna put together. We talked a little bit about this in types of fundamentals, this type, right? If you have functions that need a specific thing to be set for this, this extracts out whatever this type is of a function. And if it's successful it'll spit it out, otherwise it'll evaluate to unknown.

And this one here, it creates a type for a function. This one's actually quite interesting. Given a function, it will create a function type that has the original functions, this type, removed. And this involves a couple of steps, so let's look at this one more closely. So omit this parameter.

First of all, there's no constraint on this type parameter, which is interesting. And here we're saying if unknown extends ThisParameterType T emit T. So let's use our subtype language here. We're saying, if unknown is a subtype of the this parameter of a function, that effectively means that it's unknown.

It means that this parameter is unknown. Right, cuz this is the top type, I guess it could be any as well. But when we're saying a top type is a subtype of whatever I've got, that has to be another top type, right? This type must fit inside this type over here, and everything in JavaScript is in this type over here on the left.

So it's got to be a top type, and if so, we emit T. All right, in the case where that doesn't happen, we're gonna say, all right, if T is a function signature of some sort, we're gonna do a double infer here. Look at this. We're gonna tease out the arguments and we're gonna get the return type as well.

And all we're gonna do, is we're gonna build a new function type using those two ingredients. What's missing? We did not extract out this type as if we'd done this here. We didn't do that, we didn't grab this piece, we could do it down here, it would defeat the whole purpose.

Now we're just passing this function's, sorry, I need an infer. Now we're literally just passing the function type straight through as a whole. But the fact that we're not grabbing the this type, and we're reconstructing a new function type using the same arguments and the same return type, that means we've effectively dropped this type, we've omitted this type.

And once we've done that, we emit that. And if T does not extend this, we allow T to pass straight through. But if you think about this and look through this a little bit more closely on your own time, you will realize that this is actually an unreachable branch.

And it has to do with the fact that, oops. It has to do with the fact that you essentially would not be a function by this point. And if we look at this parameter type here, you've already got a check here that states that this has to be a function.

So, it's almost for that very last branch at the bottom. You already validated everything that was possible to validate here, you don't have to return T here, you could just return whatever. As far as I can tell, it's just impossible to actually make this evaluate out to that last thing.

So those are a few more utility types.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now