TL;DR
Just look at the code blocks and you'll probably figure out the problem.
Example Use Case
The aim is to specify an event emitter interface, which event emitter classes can implement and which can be used to safely type method calls to the emitter. As event managing basically is the same for different event emitter classes, a base emitter class shall be implemented that can be extended by other emitter classes.
In order to minimize redundancy and maximize convenience, event signatures are reduced to event names and listener signatures in one interface defined by the emitter class, passed to the emitter interface.
The following example illustrates the above described. For simplicity, the only functionality of the event emitter is binding a new listener through a method named on
.
interface MyEvents
{
foo(x: number): void;
bar(): void;
moo(a: string, b: Date): void;
}
export class MyEventEmitter implements EventEmitter<MyEvents>
{
public on(event: 'foo', listener: (x: number) => void): this;
public on(event: 'bar', listener: () => void): this;
public on(event: 'moo', listener: (a: string, b: Date) => void): this;
public on(_event: string, _listener: any): this
{ return this; }
}
Setup
- union to intersection converter from https://stackoverflow.com/a/50375286/2477364
- helpers for the on signature from https://stackoverflow.com/a/50375712/2477364 / https://stackoverflow.com/a/52368058/2477364
// union to intersection converter from https://stackoverflow.com/a/50375286/2477364
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
// helpers for the on signature from https://stackoverflow.com/a/50375712/2477364
type OnSignatures<ListenersT, ReturnT> =
{ [EventT in keyof ListenersT]: (event: EventT, listener: ListenersT[EventT]) => ReturnT };
type OnAll<ListenersT, ReturnT> =
UnionToIntersection<OnSignatures<ListenersT, ReturnT>[keyof ListenersT]>;
// the actual event emitter interface
export interface EventEmitter<ListenersT>
{
on: OnAll<ListenersT, this>;
}
This is becoming quite specific, but I thought about shrinking the example down, but I couldn't figure out a minimal example. Instead of neutralizing everything I thought it would be easier to follow with a use case.
Problem
Now to the actual problem. A class implementing base functionality of any event emitter that can be extended or mixed in by other event emitters.
export abstract class BaseEventEmitter<ListenersT> implements EventEmitter<ListenersT>
{
public on<EventT extends keyof ListenersT>(_event: EventT, _listener: ListenersT[EventT]): this
{ return this; }
}
Typescript does not like my on
method here:
Property 'on' in type 'BaseEventEmitter<ListenersT>' is not assignable to the same property in base type 'EventEmitter<ListenersT>'. Type '<EventT extends keyof ListenersT>(_event: EventT, _listener: ListenersT[EventT]) => this' is not assignable to type 'UnionToIntersection<OnSignatures<ListenersT, this>[keyof ListenersT]>'.