Hone logo
Hone
Problems

Vue 3 Integration with mitt for Event Bus

This challenge focuses on integrating mitt, a tiny pub/sub library, into a Vue 3 application to create a simple event bus. This pattern is useful for decoupling components, allowing them to communicate without direct dependencies, which can simplify complex applications.

Problem Description

You are tasked with creating a flexible event bus system within a Vue 3 application using the mitt library. This event bus will enable communication between various Vue components, such as child-to-parent, parent-to-child, or even sibling components, without them needing to know about each other's existence directly.

Key Requirements:

  1. Centralized Event Bus: Create a single, globally accessible instance of the mitt event bus.
  2. Component Integration: Implement functionality within Vue components to:
    • Emit Events: Send custom events to the bus with optional payloads.
    • Listen for Events: Subscribe to specific events from the bus.
    • Unsubscribe from Events: Ensure listeners are properly removed when components are unmounted to prevent memory leaks.
  3. Type Safety: Utilize TypeScript to ensure type safety for event names and their payloads.

Expected Behavior:

When an event is emitted from one component, any other component that is actively listening for that specific event should receive the emitted data and execute their designated handler.

Edge Cases to Consider:

  • Multiple Listeners: How does the system handle multiple components listening to the same event?
  • Unsubscribing: What happens if a component tries to unsubscribe from an event it's not listening to, or unsubscribes multiple times?
  • Event Payload Types: Ensure that different events can have different payload types, and that these are handled correctly by TypeScript.

Examples

Example 1: Simple Notification

Imagine two components: NotifierComponent and DisplayComponent. NotifierComponent sends a notification, and DisplayComponent displays it.

  • NotifierComponent (Emitting):

    // Assuming 'eventBus' is your mitt instance
    eventBus.emit('userLoggedIn', { username: 'Alice', timestamp: Date.now() });
    
  • DisplayComponent (Listening):

    // Assuming 'eventBus' is your mitt instance
    onMounted(() => {
      eventBus.on('userLoggedIn', (payload: { username: string; timestamp: number }) => {
        console.log(`User ${payload.username} logged in at ${new Date(payload.timestamp).toLocaleString()}`);
      });
    });
    
    onUnmounted(() => {
      eventBus.off('userLoggedIn'); // Or a specific handler if needed
    });
    

Output: In the browser console of the component listening to userLoggedIn, you would see a message like: User Alice logged in at 10/26/2023, 10:30:00 AM

Explanation: NotifierComponent emits the userLoggedIn event with an object containing username and timestamp. DisplayComponent registers a listener for userLoggedIn and, upon receiving the event, logs a formatted message using the provided payload. The listener is cleaned up when DisplayComponent is unmounted.

Example 2: Dynamic Data Update

Consider a DataFetcherComponent that fetches data and an InfoDisplayComponent that shows it.

  • DataFetcherComponent (Emitting):

    interface FetchResult {
      id: number;
      name: string;
      value: any;
    }
    // ... fetch data ...
    const fetchedData: FetchResult = { id: 1, name: 'Product A', value: 'Details...' };
    eventBus.emit('dataUpdated', fetchedData);
    
  • InfoDisplayComponent (Listening):

    interface FetchResult {
      id: number;
      name: string;
      value: any;
    }
    let displayData: FetchResult | null = null;
    
    onMounted(() => {
      eventBus.on('dataUpdated', (payload: FetchResult) => {
        displayData = payload;
        console.log('Received data:', displayData);
      });
    });
    
    onUnmounted(() => {
      eventBus.off('dataUpdated');
    });
    

Output: In the InfoDisplayComponent, displayData would be updated with { id: 1, name: 'Product A', value: 'Details...' }, and the console would log: Received data: { id: 1, name: 'Product A', value: 'Details...' }

Explanation: DataFetcherComponent emits the dataUpdated event with a structured FetchResult object. InfoDisplayComponent listens for this event, updates its local displayData state, and logs the received data.

Constraints

  • The mitt library must be used for the event bus implementation.
  • The solution must be implemented in TypeScript.
  • Event names should be strings.
  • Payloads for events can be of any type, but should be strongly typed when emitted and listened to.
  • All event listeners must be properly unsubscribed when the component that registered them is unmounted.
  • The event bus instance should be accessible globally or through a composable function.

Notes

  • Consider how you will define and manage the types for your events. A common approach is to use a TypeScript Record or an interface to map event names to their respective payload types.
  • Think about where you will instantiate and export your mitt event bus instance so it can be imported and used across your application. A dedicated eventBus.ts file is a good practice.
  • When implementing the .off() method, consider if you need to remove a specific listener or all listeners for a given event name. mitt supports both.
Loading editor...
typescript