3

I'm trying to add strongly typed events to an EventEmitter-like system using TypeScript.

Currently, we define our types like:

interface TypedMsg<Name, T> {
  messageType: Name;
  message: T;
}

type TypedMsgFoo = TypedMsg<'FOO', string>;
type TypedMsgBar = TypedMsg<'BAR', number>;
type EitherFooOrBar = TypedMsgFoo | TypedMsgBar;

I would like to define an interface like:

interface EventHandler<T extends TypedMsg<any, any> {
  on: (messageType: T.messageType, handler: (T.message) => void) => void;
}

But Typescript doesn't support extracting subtypes like T.messageType. Is there another way to do this?

The end goal would be to define handlers with proper typing with just:

class FooBarHandler implements EventHandler<EitherFooOrBar> {
  on(messageType: EitherFooOrBar.messageType) {...}
}
KurtPreston
  • 1,062
  • 14
  • 28

1 Answers1

6

Typescript does support extracting types of members, just the syntax is a bit unusual - it's called indexed access type operator

interface TypedMsg<Name, T> {
  messageType: Name;
  message: T;
}

type TypedMsgFoo = TypedMsg<'FOO', string>;
type TypedMsgBar = TypedMsg<'BAR', number>;
type EitherFooOrBar = TypedMsgFoo | TypedMsgBar;

interface EventHandler<T extends TypedMsg<{}, {}>> {
  on: (messageType: T['messageType'], handler: (m: T['message']) => void) => void;
}

class FooBarHandler implements EventHandler<EitherFooOrBar> {
    on(
        messageType: EitherFooOrBar['messageType'], 
        handler: (m: EitherFooOrBar['message']) => void
    ) {

    }
}

However it will soon become pretty tedious to type all these declarations with types explicitly spelled out - you will want do devise something that will allow typescript to infer types for you, for example something like this question: TypeScript type inference/narrowing challenge

Community
  • 1
  • 1
artem
  • 46,476
  • 8
  • 74
  • 78