To implement a strongly typed event system in TypeScript, we can use generics to define the types of events and the functions that handle them. Using generics ensures that events and the data passed to the functions are properly typed, enhancing code safety.
Example:
type EventMap = { 'click': { x: number; y: number; }; 'hover': { element: string; }; }; class EventEmitter<Events extends Record<string, any>> { private listeners: { [K in keyof Events]?: Array<(data: Events[K]) => void> } = {}; on<E extends keyof Events>(event: E, listener: (data: Events[E]) => void) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event]!.push(listener); } emit<E extends keyof Events>(event: E, data: Events[E]) { this.listeners[event]?.forEach(listener => listener(data)); } } const emitter = new EventEmitter<EventMap>(); emitter.on('click', (data) => { console.log(`Click at (${data.x}, ${data.y})`); }); emitter.emit('click', { x: 100, y: 200 }); // OK emitter.emit('hover', { element: 'button' }); // OK
In this example, EventMap
defines the mapping of event names to the data associated with them. EventEmitter
is a generic class that ensures strong typing for the on
and emit
methods, so we don't need to worry about type errors when registering and triggering events.