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) {...}
}
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