Intermediate TypeScript, v2

Scopes and TypeParams

Intermediate TypeScript, v2

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

The "Scopes and TypeParams" 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 discusses scopes and type parameters and explains how type parameters work similarly to variables in terms of scoping, using examples of functions and tuples. Best practices for using type parameters, emphasizing the importance of specifying constraints in the simplest way possible to maximize the flow of type information, are also discussed in this segment.


Transcript from the "Scopes and TypeParams" Lesson

>> Let's talk about scopes and type parameters. This one is not, I don't think it's particularly difficult to understand. Because thankfully, type params work just like variables. So here's an example of a function here. So we've got a function here called eatApple and it takes a callback as its last argument, right?

And so we're gonna say, receiveFruitBasket. That's another function. We're past a bowl, whatever a bowl means, the types here aren't important. We're saying, thanks for the fruit basket. And in this scope, you can access both, right? It's an argument passed to the function. In this callback here, bowl and apple can both be accessed, right?

You can always access things in outer scopes, but you can't access things in a more interscope compared to what you exist in. Like I can't access apple up here. All right, let's look at the type param equivalent. So we've got a function here. Let's observe how it works first before we start looking at how the function's defined.

So I've got a tuple creator that takes an argument and then it returns a function, and I can pass an argument into that function, and what do I get out? 3 and null, so I kind of like, I start the topple by providing the first element and it returns a function.

And then I give this finish tuple function a null and that becomes the second element of the tuple. And look at this, I can say finish tuple pass it a bunch of stuff and look at that. I get a number array here, so I'm kind of reusing that three In two places, right?

I can finish it as one tuple or I can finish it as this other tuple with an array of numbers. So let's look at the function itself. It's pretty simple, we take an argument. We return a function, that function takes an argument and this is what that inner function returns.

It's just first and last, that's it. So note that up here, if I were to create something like. Do that. There's no S up here. S is a type param that is only related to this inner function. We can't use it in the outer scope. But in this inner function, we can use both.

Type params work just like variables in terms of scoping. It's very common like you could have a class that's generic over one thing and has a type param for that, and then you could have particular methods that are generic over another thing, and you could have one of those methods return yet another function that's generic over something entirely different.

You can have scopes with type params just like you can have scopes over variable. They play by the same rules, thankfully because it makes it easy to intuit how this works. It's not exceedingly common to see this, although I would say the most common case is classes and methods, but this is sort of the functional programming example here the inner and outer function.

So let's talk about some best practices around type params. We already have our HasId and our dictionary interfaces, so I'm not gonna uncomment those. But what I'm gonna do is create two different functions, their implementation, line 109 and line 113. You can see those are the same functions do the same thing at runtime, but their type params are defined differently.

So up here, I'm saying T extends a HasId array and I take in a type param T. Here I'm saying, T extends HasId and I take in an array of Ts. Let's look at what happens when we compare the results of these two things. So not super important what these classes are, I just made some things for an interesting example.

We have a payment. We have an invoice. It's just so I can have like two different flavors of things that will not type check against each other. Here's result one with example one. Here's result two with example two and just to remind, you example one is this one, right?

It's the one that says, T extends an array of HasIds. These both taking array T, the list that comes in. It must be an array. In either case, there's the array on the right side of the tooltip. There's the array on the right side of the tooltip. It's just two different ways of expressing this.

Here's result one. It's an array of payment or invoice. So any position of that array can be either, which is interesting and then let's look at example two. So it's a list of payment or invoice. So here's the array over here. So we get a union type here.

In both cases, it's just sort of like, does it end up also being represented in the type RAM? Let's look at this one one more time. That's in both in either case, but look at the return type here. HasId or undefined and then this one, payment or invoice or undefined?

That's a big difference. Remember, we talked about how the a good generic will that type information flow through it and you will lose that type information. We're losing type information and example one HasId is a pretty lowest common denominator type, right? It's just this thing with an id property.

We can't treat these as instances of a class even, certainly not as payments and invoices. So what's happening here is it has to do with what these lists end up being. Up here, you can see that we regard these as an array of HasIds. When pop returns, it's HasId are undefined.

That's what comes out of this function, you don't see a T. I mean, you see T extends HasId there, but the way pop is defined. It's sort of the lowest common denominator that ends up flowing into this type over here. We've got an array of T's when we pop we pop out a T.

So my advice to you here is specify your constraints in the most simple way you can. It turns out that there are two constraints that we really want to apply to this argument. One is that it's got to be an array and the other is that each member of the array has to have an id property on it.

And here we're trying to do too much, I would argue. We're trying to say, let's put both of those in the type param, right? Whereas here we're saying, I'm just gonna talk about T. There is an array piece, but I can use the type of the argument to capture that information.

And so we're sort of trying to define a type param constraint at the lowest possible level and that maximizes the chance that type information will flow through your generic functions or generic code in the way you would hope where there's a minimal loss of type information.

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