Vue.js 3 Event Bus
As suggested in official docs you could use mitt library to dispatch events between components, let suppose that we a sidebar and header
which contains a button that close/open the sidebar and we need that button to toggle some property inside the sidebar component :
in main.js import that library and create an instance of that emitter and define as a global property:
Installation :
npm install --save mitt
Usage :
import { createApp } from 'vue'import App from './App.vue'import mitt from 'mitt';const emitter = mitt();const app = createApp(App);app.config.globalProperties.emitter = emitter;app.mount('#app');
in header emit the toggle-sidebar
event with some payload :
<template> <header> <button @click="toggleSidebar"/>toggle</button> </header></template><script >export default { data() { return { sidebarOpen: true }; }, methods: { toggleSidebar() { this.sidebarOpen = !this.sidebarOpen; this.emitter.emit("toggle-sidebar", this.sidebarOpen); } }};</script>
In sidebar receive the event with the payload:
<template> <aside class="sidebar" :class="{'sidebar--toggled': !isOpen}"> .... </aside></template><script>export default { name: "sidebar", data() { return { isOpen: true }; }, mounted() { this.emitter.on("toggle-sidebar", isOpen => { this.isOpen = isOpen; }); }};</script>
For those using composition api they could use emitter
as follows :
Create a file src/composables/useEmitter.js
import { getCurrentInstance } from 'vue'export default function useEmitter() { const internalInstance = getCurrentInstance(); const emitter = internalInstance.appContext.config.globalProperties.emitter; return emitter;}
And from there on you can use useEmitter
just like you would with useRouter
:
import useEmitter from '@/composables/useEmitter'export default { setup() { const emitter = useEmitter() ... } ...}
On version 3 of Vue.js, you can use either a third-party library, or use the functionality written in the publisher-subscriber(PubSub concept) programming pattern.
event.js
//events - a super-basic Javascript (publish subscribe) patternclass Event{ constructor(){ this.events = {}; } on(eventName, fn) { this.events[eventName] = this.events[eventName] || []; this.events[eventName].push(fn); } off(eventName, fn) { if (this.events[eventName]) { for (var i = 0; i < this.events[eventName].length; i++) { if (this.events[eventName][i] === fn) { this.events[eventName].splice(i, 1); break; } }; } } trigger(eventName, data) { if (this.events[eventName]) { this.events[eventName].forEach(function(fn) { fn(data); }); } }}export default new Event();
index.js
import Vue from 'vue';import $bus from '.../event.js';const app = Vue.createApp({})app.config.globalProperties.$bus = $bus;
With Vue composition and defineEmit you can even make it easier :
<!-- Parent --><script setup> import { defineEmit } from 'vue' const emit = defineEmit(['selected']) const onEmit = (data) => console.log(data)</script><template> <btnList v-for="x in y" :key="x" :emit="emit" @selected="onEmit" /></template>
<!-- Children (BtnList.vue) --><script setup> import { defineProps } from 'vue' const props = defineProps({ emit: Function })</script><template> <button v-for="x in 10" :key="x" @click="props.emit('selected', x)">Click {{ x }}</button></template>
I just showed it with one children, but you could pass though the emit function down to other children.