Vue.js 3 Event Bus

ghz 1years ago ⋅ 3139 views

Question

How to create Event Bus in Vue 3?


In Vue 2, it was:

export const bus = new Vue();



bus.$on(...)
bus.$emit(...)

In Vue 3, Vue is not a constructor anymore, and Vue.createApp({}); returns an object that has no $on and $emit methods.


Answer

As suggested in official [docs](https://v3-migration.vuejs.org/breaking- changes/events-api.html#event-bus) you could use mitt library to dispatch events between components, let suppose that we have 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](https://v3.vuejs.org/api/application- config.html#globalproperties):

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()
    ...
  }
  ...
}

Using the composition API

You could also take benefit from the new composition API and define a composable event bus :

eventBus.js

import { ref } from "vue";
const bus = ref(new Map());

export default function useEventsBus(){

    function emit(event, ...args) {
        bus.value.set(event, args);
    }

    return {
        emit,
        bus
    }
}

in component A do:

import useEventsBus from './eventBus';
...
//in script setup or inside the setup hook
const {emit}=useEventsBus()
...
 emit('sidebarCollapsed',val)

in component B :

const { bus } = useEventsBus()

watch(()=>bus.value.get('sidebarCollapsed'), (val) => {
  // destruct the parameters
    const [sidebarCollapsedBus] = val ?? []
    sidebarCollapsed.value = sidebarCollapsedBus
})