Implementation of typesafe event bus based on mitt in Vue3
Implementation of type-safe event bus based on mitt in Vue3
Published: 2025-08-13 (a year ago)
Vue

Mitt-based type security event bus implementation in Vue3

Preface

In the era of Vue3's Composition API, communication between components has become more flexible and diverse. Although props/emit, provide/inject, etc. can meet most scenarios, EventBus is still an elegant solution in some cross-component and cross-layer communication scenarios. This article will show you how to implement a type-safe event bus in your Vue3 project.

Why Choose MITT

Variations of Vue2 vs Vue3

In Vue2, we can use a Vue instance as an event bus:

javascript Copy
Vue2 Way (Deprecated)
const eventBus = new Vue()

But in Vue3, this is no longer available. We need to find new solutions.

Advantages of mitt

MITT is a lightweight event emitter library with the following features:

  • Small size: Only 200 bytes, which barely affects the package volume
  • Type Safety: Full TypeScript support
  • API Concise: Only three core methods: on, off, and emit
  • Excellent Performance: Implemented based on Map, excellent performance

Implement type safe event buses

1. Define the event type

typescript Copy
type Events = {
  'user-logout': void;
  'user-login': void;
  'search-tag': string;
};

Benefits of Type Definition:

  • Compilation-time type checking
  • IDE Smart Prompts
  • Prevent misspellings of event names
  • Clarify the event parameter type

2. Create an event bus instance

typescript Copy
import mitt from 'mitt';

export const eventBus = mitt<Events>();

3. Define event constants

typescript Copy
export const EventTypes = {
  USER_LOGOUT: 'user-logout' as const,
  USER_LOGIN: 'user-login' as const,
  SEARCH_TAG: 'search-tag' as const,
};

Advantages of Using Constants:

  • Avoid hard-coding strings
  • Easy to refactor and maintain
  • Provide code hints
  • Unified incident management

Practical Application Scenarios

1. Notification of user status changes

typescript Copy
Send a logout event
eventBus.emit(EventTypes.USER_LOGOUT);

Listen for logout events
eventBus.on(EventTypes.USER_LOGOUT, () => {
    Clean up user-related data
    Redirects to the login page
    A logout prompt is displayed
});
typescript Copy
Send a search tag
eventBus.emit(EventTypes.SEARCH_TAG, 'Vue3');

Listen for search events
eventBus.on(EventTypes.SEARCH_TAG, (tag: string) => {
    Execute the search logic
    performSearch(tag);
});

3. Topic switching notifications

typescript Copy
Theme switching components
const toggleTheme = () => {
    store.setTheme();
    eventBus.emit('theme-changed', store.theme);
};

Other components listen for topic changes
eventBus.on('theme-changed', (isDark: boolean) => {
    Update component styles
    updateComponentTheme(isDark);
});

Use in Vue Components

1. Used in composable APIs

typescript Copy
import { onMounted, onUnmounted } from 'vue';
import { eventBus, EventTypes } from '@/utils/eventBus';

export default {
  setup() {
    const handleUserLogout = () => {
      Handle logout logic
      console.log ('User logged out');
    };

    onMounted(() => {
      Register for event listening
      eventBus.on(EventTypes.USER_LOGOUT, handleUserLogout);
    });

    onUnmounted(() => {
      Clean up incident monitoring
      eventBus.off(EventTypes.USER_LOGOUT, handleUserLogout);
    });

    return {
      Expose the method of sending events
      logout: () => eventBus.emit(EventTypes.USER_LOGOUT)
    };
  }
};

2. Custom Hook Packages

typescript Copy
import { onUnmounted } from 'vue';
import { eventBus } from '@/utils/eventBus';

export function useEventBus() {
  const listeners: Array<() => void> = [];

  const on = <T extends keyof Events>(
    event: T, 
    handler: (data: Events[T]) => void
  ) => {
    eventBus.on(event, handler);
    listeners.push(() => eventBus.off(event, handler));
  };

  const emit = <T extends keyof Events>(
    event: T, 
    data: Events[T]
  ) => {
    eventBus.emit(event, data);
  };

  Automatically cleans up all listeners when components are uninstalled
  onUnmounted(() => {
    listeners.forEach(cleanup => cleanup());
  });

  return { on, emit };
}

3. Hook usage example

typescript Copy
export default {
  setup() {
    const { on, emit } = useEventBus();

    Listening to events (automatic cleanup)
    on(EventTypes.USER_LOGOUT, () => {
      console.log('User logged out');
    });

    Send an event
    const handleLogout = () => {
      emit(EventTypes.USER_LOGOUT);
    };

    return { handleLogout };
  }
};

Best Practices

1. Event naming conventions

  • Use kebab-case naming
  • Verb-noun structure: 'user-login', 'data-updated'
  • Avoid overly generic names: 'change', 'update'

2. Type definition specifications

typescript Copy
type Events = {
  No parameter events
  'app-ready': void;
  
  Base type parameters
  'user-id-changed': number;
  'message-sent': string;
  
  Object type parameters
  'user-updated': {
    id: number;
    name: string;
    email: string;
  };
  
  Union type parameters
  'status-changed': 'loading' | 'success' | 'error';
};

3. Memory leak protection

typescript Copy
Example of error: Forgetting to clean up the listener
eventBus.on('some-event', handler); It can cause a memory leak

Correct example: clean up in time
onUnmounted(() => {
  eventBus.off('some-event', handler);
});

4. Debugging and logging

typescript Copy
Event logs are added in the development environment
if (import.meta.env.DEV) {
  const originalEmit = eventBus.emit;
  eventBus.emit = (event, data) => {
    console.log(`[EventBus] ${event}:`, data);
    return originalEmit.call(eventBus, event, data);
  };
}

Performance Optimization

1. Event listener management

  • Clean up unwanted monitors in a timely manner
  • Avoid repeatedly registering listeners in a loop
  • Use weak references to avoid memory leaks

2. Event frequency control

typescript Copy
import { debounce } from 'lodash-es';

Stabilization handles high-frequency events
const debouncedHandler = debounce((data) => {
  Processing logic
}, 300);

eventBus.on('scroll-event', debouncedHandler);

Comparison with other communication methods

EventBus vs Props/Emit

  • EventBus: Suitable for cross-layer, loosely coupled communication
  • Props/Emit: Suitable for direct communication between parent and child components

EventBus vs Provide/Inject

  • EventBus: Global events, any component can listen to them
  • Provide/Inject: Hierarchical dependencies, suitable for dependency injection

EventBus vs Pinia/Vuex

  • EventBus: Lightweight event communication
  • State Management: Complex state management and persistence

Considerations

1. Avoid abuse

EventBus, while flexible, should not be the preferred solution for component communication. Priority:

  • Props/Emit (Father-Child Communication)
  • Provide/Inject
  • State Management (Complex State)

2. Event naming conflicts

  • Use namespaces: 'user:login', 'article:created'
  • Establish event naming conventions
  • Clean up unwanted events regularly

3. Difficulty in debugging

  • Add event logs
  • Use Vue DevTools
  • Create event documentation

Summary

The mitt-based event bus provides a lightweight, type-safe component communication solution for the Vue3 project. With proper type definitions, specification usage, and appropriate performance optimizations, we can build an event communication system that is both flexible and maintainable.

Core Benefits:

  • Type Safety: Full TypeScript support
  • Lightweight and Efficient: Extremely small size and excellent performance
  • Simple to use: Clear API and good development experience
  • Easy Maintenance: Standardized event management and automated cleanup mechanisms

In real-world projects, EventBus should be used as a complementary option in the component communication toolbox, working with other communication methods to build high-quality Vue3 applications.


*Event bus is a powerful tool for component communication, but the key lies in rational use and standardized management. *