JAVASCRIPT
Create a Simple Event Bus Composable
Build a lightweight, reusable event bus using Vue 3's Composition API to facilitate communication between unrelated components without prop drilling or global state.
<!-- EventBusEmitter.vue -->
<template>
<div style="border: 1px solid gray; padding: 10px; margin: 10px;">
<h3>Event Emitter Component</h3>
<button @click="emitCustomEvent">Emit 'my-custom-event'</button>
<p>Message to emit: <input type="text" v-model="messageToEmit"></p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useEventBus } from './useEventBus.js';
const { emit } = useEventBus();
const messageToEmit = ref('Hello from Emitter!');
const emitCustomEvent = () => {
emit('my-custom-event', messageToEmit.value);
console.log('Event emitted:', messageToEmit.value);
};
</script>
<!-- EventBusListener.vue -->
<template>
<div style="border: 1px solid gray; padding: 10px; margin: 10px;">
<h3>Event Listener Component</h3>
<p>Received Message: {{ receivedMessage }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { useEventBus } from './useEventBus.js';
const { on, off } = useEventBus();
const receivedMessage = ref('No event received yet.');
const handleCustomEvent = (payload) => {
receivedMessage.value = payload;
console.log('Event received:', payload);
};
onMounted(() => {
on('my-custom-event', handleCustomEvent);
});
onUnmounted(() => {
off('my-custom-event', handleCustomEvent);
});
</script>
<!-- useEventBus.js (composable file) -->
import { getCurrentInstance } from 'vue';
// Use a symbol for the global property key to avoid collisions
const EVENT_BUS_GLOBAL_KEY = Symbol('myAppEventBus');
export function useEventBus() {
const app = getCurrentInstance()?.appContext.app;
if (!app) {
// Should not happen if called in setup, but good for safety
console.warn('useEventBus must be called within a component setup context.');
return { on: () => {}, off: () => {}, emit: () => {} };
}
// Ensure the event bus Map is initialized on global properties only once per app instance
if (!app.config.globalProperties[EVENT_BUS_GLOBAL_KEY]) {
app.config.globalProperties[EVENT_BUS_GLOBAL_KEY] = new Map();
}
const internalBus = app.config.globalProperties[EVENT_BUS_GLOBAL_KEY];
const on = (event, callback) => {
if (!internalBus.has(event)) {
internalBus.set(event, new Set());
}
internalBus.get(event).add(callback);
};
const off = (event, callback) => {
if (internalBus.has(event)) {
internalBus.get(event).delete(callback);
// Optional: Clean up the event's Set if it becomes empty
if (internalBus.get(event).size === 0) {
internalBus.delete(event);
}
}
};
const emit = (event, ...args) => {
if (internalBus.has(event)) {
// Iterate over a copy of the Set to prevent issues if listeners modify it
[...internalBus.get(event)].forEach(callback => callback(...args));
}
};
return { on, off, emit };
}
How it works: This snippet provides a reusable Vue 3 Composable (`useEventBus.js`) that implements a simple event bus pattern. It allows components to communicate with each other regardless of their position in the component tree, without relying on props or emits. The `on` function registers a listener for an event, `off` removes it, and `emit` triggers an event, passing any number of arguments. The event bus itself is stored as a `Map` within Vue's global properties, ensuring it's a singleton accessible across the entire application, making it useful for global notifications or inter-component communication that doesn't fit standard prop/emit patterns. Listeners should be cleaned up using `onUnmounted`.