Reputation: 57
I am trying to make my code more readable and concise.
The biggest problem I am currently having is with the EventEmitter
class. Every time I create a new class that uses EventEmitter
I have to declare all the functions of the EventEmitter
to make the class easier to understand and use.
Ex:
interface Hello {
addListener(event: 'hi', listener: (message: string) => void): this;
on(event: 'hi', listener: (message: string) => void): this;
...
}
class Hello extends EventEmitter {
constructor() { super(); }
}
I have searched around for a solution but I couldn't find anything that suits me so I tryed to come up with my own.
interface EventEmitterEvents {
[event: string]: any[];
}
interface EventEmitterType<T extends EventEmitterEvents> extends EventEmitter {
addListener<K extends keyof T>(event: K, listener: (...args: T[K]) => void): this;
on<K extends keyof T>(event: K, listener: (...args: T[K]) => void): this;
...
}
class Hello extends EventEmitter implements EventEmitterType<{'hi': [string]}> { ... }
But when I try to implement the interface EventEmitterType it throws an error
types of property 'addListener' are incompatible
I have figured out that for some reason in the 'addListener'
and functions alike, type event
is said to be 'string' | 'symbol' | 'number'
which is incompatible with the EventEmitter
where it is 'string' | 'symbol'
but in the EventEmitterEvents
I have defined that event is of type 'string'
.
Question: Is there any way to fix this, and if not, is there any other way of recreating this functionality (not having to type all those functions)?
Edit:
If I set event argument to 'string'
it will still throw error because of listener which is also incompatible with addListener
saying 'any[]' is not assignable to type 'T[K]'
.
Upvotes: 1
Views: 3629
Reputation: 1550
I really like the functionality that you can get from getting this sort of extension right. Anyhow, here are some ideas I would like to share that might help:
interface EventEmitterEvents {
[event: string]: any[];
}
abstract class EventEmitterType<T extends EventEmitterEvents> extends EventEmitter {
protected constructor() {
super();
// do stuff here
}
addListener<K extends keyof T | symbol>(event: K, listener: (...args: T[Extract<string, K>]) => void) {
// do stuff here
return super.addListener(event, listener);
}
on<K extends keyof T | symbol>(event: K, listener: (...args: T[Extract<string, K>]) => void) {
// do stuff here
return super.on(event, listener);
}
}
class Hello extends EventEmitterType<{ hi: [string] }> {}
/*
* So the idea is to define a function that will convert the event emitter to your
* desired design by strapping on the new stuff and ideas that you have.
*/
// So we have our interface from your sample code
interface EventEmitterEvents {
[event: string]: any[];
}
interface EventEmitterType<T extends EventEmitterEvents> {
addListener<K extends keyof T>(event: K, listener: (...args: T[K]) => void): this;
on<K extends keyof T>(event: K, listener: (...args: T[K]) => void): this;
...
}
// then you define a function that will cast your interface onto the event emitter
function bootstrap<T extends EventEmitterEvents>(emitter: EventEmitter) {
return emitter as EventEmitterType<T>
}
// this introduces a grey area in your code that you might not like though.
I hope this is of some use to your goal here.
Upvotes: 3
Reputation: 76
Maybe narrowing the type of event names to string
like Extract<keyof T, string>
could help.
Or if you just want the result of keyof
operator to be a string, add configuration with "keyofStringsOnly": true
in tsconfig.json
.
Upvotes: 0