I'm implementing event emitter
but ran into a problem with typescript typing. Please tell me how to make this work.
export type IEventMap = {
"mount": () => void;
"mounted": () => void;
"unmount": () => void;
"unmounted": () => void;
"test1": (stringArg: string) => void;
"test2": (numberArg: number) => void;
};
export type IEventNames = keyof IEventMap;
export type IEventFn<K extends IEventNames> = IEventMap[K];
export type IEventListeners = { [K in IEventNames]?: Set<IEventFn<K>> };
export default class Events {
private readonly listeners: IEventListeners = {};
public on <K extends IEventNames> (eventName: K, callback: IEventFn<K>) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = new Set();
}
this.listeners[eventName].add(callback);
}
public off <K extends IEventNames> (eventName: K, callback: IEventFn<K>) {
if (this.listeners[eventName]) {
this.listeners[eventName].delete(callback);
}
}
public emit <K extends IEventNames> (eventName: K, ...args: Parameters<IEventFn<K>>) {
if (this.listeners[eventName]) {
this.listeners[eventName].forEach(callback => callback(...args));
}
}
}
// test
const events = new Events();
events.on("mount", () => {
console.log("mount");
});
events.on("test1", arg => {
console.log(arg);
});
events.on("test2", arg => {
console.log(arg);
});
events.emit("mount");
events.emit("test1", "test string");
events.emit("test2", 1);
Errors
Type 'Set<IEventFn>' is not assignable to type 'IEventListeners[K]'. Type 'Set<IEventFn>' is not assignable to type 'Set<() => void> & Set<() => void> & Set<() => void> & Set<() => void> & Set<(stringArg: string) => void> & Set<(numberArg: number) => void>'. Type 'Set<IEventFn>' is not assignable to type 'Set<() => void>'. Type 'IEventFn' is not assignable to type '() => void'. Type '(() => void) | (() => void) | (() => void) | (() => void) | ((stringArg: string) => void) | ((numberArg: number) => void)' is not assignable to type '() => void'. Type '(stringArg: string) => void' is not assignable to type '() => void'.
Object is possibly 'undefined'. Object is possibly 'undefined'. Object is possibly 'undefined'.
A spread argument must either have a tuple type or be passed to a rest parameter.
CodePudding user response:
- interchangeable callbacks should actually be rest-args, so
export type IEventFn<K extends IEventNames> = (...a: Parameters<IEventMap[K]>) => void;
fixes half of errors
- ts doesn't remember type if if by whatever reason, just use
?.
this.listeners[eventName]?.delete(callback);
- ts doesn't want do assign mismatching types, type down so types match
let l: { [KK in K]?: Set<IEventFn<KK>> } = this.listeners;
(l[eventName] ??= new Set<IEventFn<K>>()).add(callback);