4

I'm creating an EventEmitter in typescript, and I can't figure out a way to do the following:

Say I have an interface like this:

interface EventEmitterSubscription { dispose(): void }

// here it is
interface EventEmitter<T extends { [key: string]: any }> {
  onAnyEvent(callback: (event: { type: ???, payload: ??? }) => void): EventEmitterSubscription
  // ...
}

I can't find a way to type the onAnyEvent callback such that, for example, for an eventEmitter like this:

EventEmitter<{ onNumberReceived: number, onCatReceived: { cat: ICat }, onPersonNameReceived: string }>

The onAnyEvent field would have the type

onAnyEvent(callback: (event: { type: 'onNumberReceived', payload: number } | { type: 'onCatReceived', payload: { cat: ICat } } | { type: 'onPersonNameReceived', payload: string }) => void): EventEmitterSubscription

Currently my implementation has the interface look like:

onAnyEvent(callback: (event: { type: keyof T, payload: T[keyof T] }) => void): EventEmitterSubscription

except that currently doesn't work, as for example that would produce the types onAnyEvent(callback: (event: { type: 'onNumberReceived', payload: number } | { type: 'onNumberReceived', payload: { cat: ICat } } | /* ... */) => void): EventEmitterSubscription

So how can I type the onAnyEvent field?

1 Answer 1

7

One possible way to create a union is to have a mapped type with each property having the type of one element of desired union, like this:

type EventTypeMap<T extends { [key: string]: {} }> =
    { [K in keyof T]: { type: K; payload: T[K] } };

Then you can define generic union type

type CallbackEventTypes<T extends { [key: string]: {} }> =
     EventTypeMap<T>[keyof EventTypeMap<T>] 

and use it

interface EventEmitterSubscription { dispose(): void }

interface EventEmitter<T extends { [key: string]: {} }> {
  onAnyEvent(callback: (event: CallbackEventTypes<T>) => void): EventEmitterSubscription
  // ...
}

interface ICat { meow() }

type Emitter = EventEmitter<{ onNumberReceived: number, onCatReceived: { cat: ICat }, onPersonNameReceived: string }>

type HandlerType = Emitter['onAnyEvent'];

// inferred as
//type HandlerType = 
// (callback: (event: { type: "onNumberReceived"; payload: number; }
//                  | { type: "onCatReceived"; payload: { cat: ICat; }; }
//                  | { type: "onPersonNameReceived"; payload: string; }
//            ) => void) => EventEmitterSubscription
Sign up to request clarification or add additional context in comments.

1 Comment

This is just what I was looking for! Excellent!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.