Use a Tuple to Represent a Dynamic Number of Arguments
The first thing we need to do is capture the event and extract its type.
To do this, we'll add a generic with a type
TEventKey that extends the
keyof events to capture the event.
Then we can can put
TEventKey into the event:
export const sendEvent = <TEventKey extends keyof Events>(
0:01 The first thing we need to do is capture this event here and work out a way to extract out the type that comes from this event. Let's do that first. We're going to say, TEventKey extends keyof Events. Now, we can put that into the event here. We've got TEventKey.
0:20 Now, sendEvent("click"), we've got this click being captured. That's good. Now these args, let's just say we've got one arg for now, and this arg is going to be Events TEventKey .
0:33 This gets us somewhere, because now what's going to happen is we've got click and we're getting this arg inside here. The arg now is going into basically indexing into Events using TEventKey and pulling out this object. Except this doesn't work really, because we need a dynamic number of arguments. It expects two arguments, but got one.
0:54 It's expecting us to pass in undefined here, which is super gross as an API. What we need to do is to revert this a little bit and say args is this. Now, args needs to be an array type or it can be a tuple type. What this means is we have access to Events TEventKey and we can say, if this extends undefined, then what we're going to do is return an empty array.
1:23 This means the args will basically be an empty array in that slot. Otherwise, we're going to return a tuple with a member and it's going to be Events TEventKey just here. This may look mad. What we're checking here is we have access to the x and y here. X, y number just there.
1:48 We can see that if that extends undefined, conditional check, checking if it is assignable to undefined, then we return this. Otherwise, we return a tuple where the first member is this Events TEventKey . Super nice.
2:04 This sendEvent("focus"), in these args, even though you can't see them in there, the args are actually an empty array being spread into the argument. You end up with no extra arguments. Otherwise, you end up with these args_ inside here. You see that? It automatically generates you a name, which is cool.
2:26 If we were to imagine, we put three of these into Events TEventKey and then let's say, you just got to pass in a string at the end. Now, it's expecting you to pass in x and y again. The first, x and y, then the second x and y, and the third is a string. Really cool. This allows you to be really dynamic with the arguments that you're passing into your functions.
2:49 There's one more thing too, which is this args_0 thing isn't very pretty. Ideally what we should do here is we should somehow find a way to make this nicer looking. If we could change that to payload or something, I think it would read a lot easier.
3:02 Well, we can use a named tuple in here and this named tuple doesn't do anything in TypeScript. One named tuple is assignable to another named tuple. We can call this anything we want, but it just gives TypeScript a hint of what we want to call this argument. That payload ends up being sucked into the parameters of that new function and it all works out beautifully.
3:29 This pattern is super duper useful when you have really dynamic functions that you don't want to express with function overloads and this lets you be really specific with the arguments based on a generic that's passed in.