Reputation: 951
How can I refer to a specific function argument right inside the function signature?
The image above is a fake just for demonstration! I provide name
and arg
and need to retrieve some type info for arg by stuffing name into another function. But I don't know how to refer to name inside the function signature.
Context:
I'm working on a simple event manager. You can define event-names, assign callbacks to those and fire events. Callbacks may have an arbitrary number of arguments.
(The example below is very reduced, the real implementation is more complex, e.g. events can have multiple callbacks etc)
type CallbackType = (...args: any[]) => void;
const eventRegistry: Map<string, CallbackType> = new Map();
const registerEvent = (name: string, callback: CallbackType): void => {
eventRegistry.set(name, callback);
}
const fireEvent = (name: string, ...args: any[]): void => {
eventRegistry.get(name)!(...args);
}
some example callbacks
const foo = (input: number): void => {
console.log("foo here:", input);
}
const bar = (input1: boolean, input2: string): void => {
console.log("bar here:", input1, input2);
}
event registration and event firing
registerEvent("someEventName", foo);
registerEvent("anotherEventName", bar);
fireEvent("someEventName", 42);
fireEvent("anotherEventName", true, "Hello World");
Here's a link to a typescript playground.
The implementation above is not type-safe. You don't get any hints in fireEvent
what arguments the callbacks expect.
So I tried using typescripts Parameters utility type:
const fireEvent = (name: string, ...args: Parameters<typeof eventRegistry.get(name)>): void => {
eventRegistry.get(name)!(...args);
}
But that got me nowhere. To correctly type args I first need to get the callback assigned to the event but I don't know how to refer to name.
Upvotes: 0
Views: 407
Reputation: 250196
You can use Parameters
to get the arguments of a function, and you can use spread to spread the arguments back to a function. You can even use generic type parameters and index types to get the type of the function from an object typoe:
type EventRegistry = {
someEventName: typeof foo
anotherEventName: typeof bar
}
const eventRegistry: Map<string, CallbackType> = new Map();
const fireEvent = <K extends keyof EventRegistry>(name: K, ...args: Parameters<EventRegistry[K]>): void => {
eventRegistry.get(name)!(...args);
}
fireEvent("anotherEventName", true, "") // ✅
fireEvent("anotherEventName", true, 0) // ❌
Your real problem here is that registerEvent
does not in any way leave a mark on the type of fireEvent
. This means that fireEvent
does not know what events were registered, if we want this information at compile time, we need to flow it from the register calls to the fireEvent
calls.
Here is my stab at it, not a definitive solution, but a starting point:
class EventRegistry<T extends Record<string, CallbackType> = {}>{
eventRegistry: Map<string, CallbackType> = new Map();
registerEvent<K extends string, C extends CallbackType>(name: K, callback: C):EventRegistry<T & Record<K, C>> {
this.eventRegistry.set(name, callback);
return this;
}
fireEvent<K extends keyof T & string>(name: K, ...args: Parameters<T[K]>): void {
this.eventRegistry.get(name)!(...args);
}
const reg = new EventRegistry()
.registerEvent("someEventName", foo)
.registerEvent("anotherEventName", bar);
reg.fireEvent("someEventName", 42); // ✅
reg.fireEvent("anotherEventName", true, "Hello World"); // ✅
reg.fireEvent("anotherEventName", true, 0); // ❌
Upvotes: 1