Reminiscent of removing native API fetching in Vue 2 in favor of encouraging the use of third party libraries (most recommended being Axios), Vue 3 also has a breaking change: event bussing. Well, technically, Vue just wants you to only emit from parent⟵child
(and parent⟶child
bussing would be through the use of props). They’ve never officially encouraged developers to create an event bus outside of the main Vue instance because some consider it anti-pattern to the Vue methodology; outside of the parent⟷child
emit/prop pattern, they encourage developers to use VueX when that pattern is too limiting. The downside is VueX can be overkill for a lot of smaller projects, so you’ll see a ton of event bus articles and tutorials using this publish-subscribe or “pubsub” pattern through a Google search.
Previously, you’d create an event bus in the base of your project through a separate Vue instance, which you’d then use throughout your application to bus events/data between parent and child using their Events API:
/* main.js */
import Vue from 'vue';
export const bus = new Vue();
... init main Vue application ...
/* ComponentA.vue */
<template>
<div>
<button @click="bus.$emit('say-hello', 'Hey, Stranger!')">Emit Event!</button>
</div>
</template>
<script>
import { bus } from '../main'
export default {
name: 'ComponentA',
...
/* ComponentB.vue */
<script>
import { bus } from '../main';
export default {
name: 'ComponentB',
created (){
bus.$on('say-hello', (greeting) => alert(greeting))
}
}
</script>
In Vue 3 this is no longer possible because while $emit
still works for child->parent communication, $on
, $off
, and $once
are no longer available to the instance (more about that here).
Luckily it’s really simple to add an external library to handle it for us in much the same way. In this example, I’ll use Mitt.
I’ll use emitter
instead of bus
which follows their example code and hopefully makes it easier to spot the difference having used bus
in the previous example.
/* main.js */
import App from '@/App.vue'
import mitt from 'mitt'
import { createApp } from 'vue'
const emitter = mitt()
const app = createApp(App)
app.config.globalProperties.emitter = emitter
app.mount('#app')
/* ComponentA.vue */
<template>
<div>
<button @click="emitter.emit('say-hello', 'Hey, Stranger!')">Emit Event!</button>
</div>
</template>
<script>
export default {
name: 'ComponentA',
...
/* ComponentB.vue */
<script>
import { bus } from '../main';
export default {
name: 'ComponentB',
created (){
this.emitter.on('say-hello', (greeting) => alert(greeting))
}
}
</script>
Ultimately, like any breaking change, it can be a hassle to update projects but in the end it ends up being pretty simple and the code looks largely the same. While certainly Vue’s developers have their opinions on how they’d like to see Vue used, they’ve always done a great job of offering up compromises where their framework is still flexible when you add features or patterns outside of their normal workflow. The developer wins, too, because libraries like Mitt can focus on supporting features that are outside of Vue’s feature scope.
#vue #emit #publish-subscribe #pubsub #bus #event