TypeScript 5+ Fundamentals, v4

TypeScript 5+ Fundamentals, v4 Constructables & Function Overloads

Topics:
Check out a free preview of the full TypeScript 5+ Fundamentals, v4 course:
The "Constructables & Function Overloads" Lesson is part of the full, TypeScript 5+ Fundamentals, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses constructables and explains how to create a construct signature by adding the "new" keyword in front of a call signature. The concept of function overloads and a demonstration of how they can be used to handle different types of event handlers in a centralized event listener is also covered in this segment.

Get Unlimited Access Now

Transcript from the "Constructables & Function Overloads" Lesson

[00:00:00]
>> Let's talk about constructables, so here's an example of a date constructor, this is exactly the same, this would be a call signature. Now, it's a construct signature, just add the word new in front of it, this is for newables. Anything that you can use to create an instance, classes, of course, are probably the first thing you think of, right?

[00:00:24] So here we can say MyDateConstructor is a DateConstructor, and I'm gonna take Date, not new date, but literally Date. Now, I can do this, And this is a date, this here, this is the interface of the class. So this is a construct signature, I would say this is fairly rare.

[00:00:55] It's more common certainly to write call signatures, cuz anytime you have a call back, things like that. If you're writing higher order functions, you're gonna end up with a call signature, but they're no more difficult, just remember to add the new keyword. So another interesting thing that has to do with call signatures is a concept called function overloads.

[00:01:19] Let's imagine a scenario where we have a form, and, Maybe we have a central event listener, which is, as I understand it, best practice, right? So you don't have a bunch of different event listeners to set up and all those separate callbacks to maintain. We can have kind of a global event listener that dispatches events appropriately.

[00:01:43] And we want to handle events that could come from a form or an iframe. So with that, let's give it a shot and let's think about the different types of handlers we would need. So a form of SubmitHandler takes FormData as an argument, and a MessageHandler, which would be a post message channel, right?

[00:02:09] Or you're communicating with an iframe. You'd have this on message event, and you'd pass in, something that looks like this, this is the type of a message handler. So let's say we wanna have a function that could handle both, they could allow us to pass as one argument, the element.

[00:02:36] The second argument is the type, and we kind of registered those together somehow. So we can get an iframe, there's an HTMLIFrameElement, and then we can call handleMainEvent, and if we look at the type of val:, well, it has an any type. And, all right, well, I don't know, I kind of expected it to infer more here, but we can keep going.

[00:03:10] Cuz I think we get to the same place here, so I'll tell you what I expected to see, these are both single argument functions, they return void. So I kind of expected to see something like FormData or MessageEvent as an argument with a void return type. But it seems to have bailed, and this is now an implicit any.

[00:03:36] In either case, we kinda wanna allow for two specific things that could happen here, right? Where I pass in my form element, and I get a form submit handler, or I pass in my iframe element, and I get a message handler. We don't wanna mix and match, we don't want these two, or these two here, right?

[00:03:58] We wanna have one pair on the left or the pair on the right, and we can accomplish this with function overloads. So the way this works is we're going to create multiple function heads, we would call these. So the thing to note here is these are what we call function heads, they don't have a body, there's no implementation here.

[00:04:26] We're gonna grab these two, and we're gonna put them above what we would call the implementation of the function. And we'll need a return type here, All right, so if we look at how we're allowed to call this now, You see this 1 out of 2 and 2 out of 2?

[00:04:56] Look at that, first argument is a FormElement, the second argument is our form SubmitHandler. Here's two out of two, got the iframe element, and the message handler. So although, the underlying implementation of this function would technically allow us to mix and match, right, they're just union types. There's nothing in this function itself that sort of expresses any relationship between the first and second arguments.

[00:05:25] What's happened is we've hidden this, this is not callable in and of itself, you can only call this function through one of these two heads. And so we've sort of limited the scope, the combinations of what can be done here. And so now, this should be typed and it is, there's a message event, but if I were to type in, we got a form, Look at that, it's FormData, it switched on us, because it knows you have to do one pair or the other pair.

[00:06:04] It's sort of using the first argument to figure out the second argument. And you can think of it almost, when we call this function, it's going through the various function heads one by one to find one that matches. But the first argument we're passing in, and then it'll try to find one that also matches the second argument, and then it'll let us use that.

[00:06:27] But if we were to delete this, for example, so now, there's no iframe element here. If we were to switch back, This doesn't exist, you can't pass a frame in, if we were to look at this, it's just the form element. So you see how this implementation of the function, the last function head that has an actual, like this body here.

[00:07:02] We can't directly access that, we kind of only access it through the heads when you do this overloading technique. It is a really powerful idea, particularly because there are other ways of doing this that just are incredibly advanced in terms of types. This kind of makes an approach, what we're looking at here, much more accessible without getting into generics where we have types described in terms of other parameterized types.

[00:07:33] You don't wanna be reaching for that unless it's important and necessary. One other thing I should mention here, The types of the heads, these being the heads, they have to align with the actual implementation here. So if we were to do this, we wouldn't be able to do this function head, right?

[00:08:10] Each of these parameters here, each of the function types represented by the heads here, they have to fit within the underlying implementation. So once we add that back, we should be good. It's common, sometimes people even do this, and then they'll cast within the function implementation, knowing that they can sort of sort out what the type should be.

[00:08:34] If you have a wide range of things, and you don't wanna turn this into a crazy expanded type. The reason it's somewhat a little bit safer here is no one can directly access this. Like you have a guarantee that if someone gets into this, they came through one of these two heads.

[00:08:54] So it's not the same as just having an any out in the wild. You have some narrow set of pathways that someone must have taken to get into the function.
>> Is there a reason that we have to specify the types with a union instead of just letting it infer from the overload?

[00:09:16]
>> The, Typescriptive does not infer that way, so, I mean, that would be the reason. It's more that the type of the function itself is processed first, and then the heads are checked for compatibility with the underlying implementation. So I think it's sort of an order of operations issue where the real function comes first, and then the heads have to be compatible with that function.

[00:09:48] So the overloads, those heads don't really influence the type of the function itself. And it would also make it really kind of hard to maintain one of these, if sort of the arguments would sort of get altered based on different signatures above. You wanna get really tight about this being well-typed, and then you can sort of constrain different usage patterns using the function heads.

[00:10:16] You wouldn't wanna add another function head, and then everything inside your function lights up, because there's implicitly a new thing that could be there.