I am trying to describe a dictionary of event handlers (with different payload shapes) like this:
type Events = 'foo' | 'bar';
type EventsPayload = { foo: string, bar: number };
type EventHandler<Event extends Events> = (args: EventsPayload[Event]) => void;
const eventHandlers: { [Event in Events]?: EventHandler<Event> } = {};
export function addEventHandler<Event extends Events>(
name: Event,
handler: EventHandler<Event>,
) {
eventHandlers[name] = handler;
}
This is giving me the error:
Type 'EventHandler' is not assignable to type '{ foo?: EventHandler<"foo"> | undefined; bar?: EventHandler<"bar"> | undefined; }[Event]'.
If I remove the generics when I insert the handler method it works:
eventHandlers.foo = (payload: string) => {};
You can make it work by referring to the type of eventHandlers
when defining the type of the handler
parameter. (This is what they do for addEventListener
in lib.dom.d.ts
, for example.) I find it easiest to do that by using a type alias for the type of the eventHandlers
object:
type Events = "foo" | "bar";
type EventsPayload = { foo: string; bar: number };
type EventHandler<Event extends Events> = (args: EventsPayload[Event]) => void;
// *** Define the type
type EventHandlers = {
[Event in Events]?: EventHandler<Event>;
};
const eventHandlers: EventHandlers = {};
// *** Use it for `handler`
export function addEventHandler<Event extends Events>(
name: Event,
handler: EventHandlers[Event]
) {
eventHandlers[name] = handler;
}
You don't actually need to have the type alias, though, you could use typeof eventHandlers
instead:
type Events = "foo" | "bar";
type EventsPayload = { foo: string; bar: number };
type EventHandler<Event extends Events> = (args: EventsPayload[Event]) => void;
const eventHandlers: { [Event in Events]?: EventHandler<Event> } = {};
export function addEventHandler<Event extends Events>(
name: Event,
handler: typeof eventHandlers[Event]
) {
eventHandlers[name] = handler;
}
Subjectively speaking, to me it's clearer if we have a type alias to work with.