I have a function that accepts a Record with event listener callbacks as values. I'd like to enforce that the first argument of an event callback within this record is typed as a CustomEvent<PayloadThatConformsToABaseType>. I've tried the following:
type EventPayload = string | number;
interface CustomEvent<T> { payload: T }
function registerListeners<T extends EventPayload>(
listeners: Record<string, (e: CustomEvent<T>) => void>
) {}
// Gets inferred as `registerListeners<"hello">(listeners: Record<string, (e: CustomEvent<"hello">) => void>): void`
// Type '"hello"' is not assignable to type '2'.
registerListeners({
eventOne: (event: CustomEvent<'hello'>) => {},
eventTwo: (event: CustomEvent<2>) => {}
})
function registerListeners(
listeners: Record<string, <T extends EventPayload>(e: CustomEvent<T>) => void>
) {}
// Gets inferred as `registerListeners(listeners: Record<string, <T extends EventPayload>(e: CustomEvent<T>) => void>): void`
// Type 'EventPayload' is not assignable to type '"hello"'
registerListeners({
eventOne: (event: CustomEvent<'hello'>) => {},
eventTwo: (event: CustomEvent<2>) => {}
})
function registerListeners(listeners: Record<string, (e: CustomEvent<any>) => void>) {}
// Works but I can no longer constrain the event params
registerListeners({
eventOne: (event: CustomEvent<'hello'>) => {},
eventTwo: (event: CustomEvent<{a: string}>) => {}
})
How do I enforce that the callbacks passed to registerListeners accept an acceptable event as argument?
It looks like you want to use a mapped type where the generic type parameter
Tcorresponds to the type argument toCustomEventfor each key (e.g., for your example, the type argument forTwould be{ eventOne: "hello"; eventTwo: 2; }. Like this:Because this is a homomorphic mapped type (see What does "homomorphic mapped type" mean?) then the compiler knows how to infer from it when you call
registerListeners():If you inspect with IntelliSense, you'll see that
Tis inferred as{ eventOne: "hello"; eventTwo: 2; }as expected. Depending on the use case you could then go on to compute other types that depend onT(in general the function might return something that needs to keep track of which events go with which keys).Playground link to code