Skip to content

Events API
breaking

Overview

$on, $off and $once instance methods are removed. Component instances no longer implement the event emitter interface.

2.x Syntax

In 2.x, a Vue instance could be used to trigger handlers attached imperatively via the event emitter API ($on, $off and $once). This could be used to create an event bus to create global event listeners used across the whole application:

js
// eventBus.js

const eventBus = new Vue()

export default eventBus
js
// ChildComponent.vue
import eventBus from './eventBus'

export default {
  mounted() {
    // adding eventBus listener
    eventBus.$on('custom-event', () => {
      console.log('Custom event triggered!')
    })
  },
  beforeDestroy() {
    // removing eventBus listener
    eventBus.$off('custom-event')
  }
}
js
// ParentComponent.vue
import eventBus from './eventBus'

export default {
  methods: {
    callGlobalCustomEvent() {
      eventBus.$emit('custom-event') // if ChildComponent is mounted, we will have a message in the console
    }
  }
}

3.x Update

We removed $on, $off and $once methods from the instance completely. $emit is still a part of the existing API as it's used to trigger event handlers declaratively attached by a parent component.

Migration Strategy

Migration build flag: INSTANCE_EVENT_EMITTER

In Vue 3, it is no longer possible to use these APIs to listen to a component's own emitted events from within a component. There is no migration path for that use case.

Root Component Events

Static event listeners can be added to the root component by passing them as props to createApp:

js
createApp(App, {
  // Listen for the 'expand' event
  onExpand() {
    console.log('expand')
  }
})

Event Bus

The event bus pattern can be replaced by using an external library implementing the event emitter interface, for example mitt or tiny-emitter.

Example:

js
// eventBus.js
import emitter from 'tiny-emitter/instance'

export default {
  $on: (...args) => emitter.on(...args),
  $once: (...args) => emitter.once(...args),
  $off: (...args) => emitter.off(...args),
  $emit: (...args) => emitter.emit(...args)
}

This provides the same event emitter API as in Vue 2.

In most circumstances, using a global event bus for communicating between components is discouraged. While it is often the simplest solution in the short term, it almost invariably proves to be a maintenance headache in the long term. Depending on the circumstances, there are various alternatives to using an event bus:

  • Props and events should be your first choice for parent-child communication. Siblings can communicate via their parent.
  • Provide / inject allow a component to communicate with its slot contents. This is useful for tightly-coupled components that are always used together.
  • Provide / inject can also be used for long-distance communication between components. It can help to avoid 'prop drilling', where props need to be passed down through many levels of components that don't need those props themselves.
  • Prop drilling can also be avoided by refactoring to use slots. If an interim component doesn't need the props then it might indicate a problem with separation of concerns. Introducing a slot in that component allows the parent to create the content directly, so that props can be passed without the interim component needing to get involved.
  • Global state management, such as Pinia.