On Vue Events

This is the English version of this article.

この記事の日本語版はこちらです。

In Vue, there are two mechanisms used to communicate between components, “props” and “events”. One of them is better than the other.

Vue props are all-purpose arguments that any component can use to receive information about its environment in a controlled, testable way. For example, this title component receives its content via a prop:

<template>
  <div>
    {{ title }}
  </div>
</template>
export default Vue.extend({
  props: {
    title: {
      type: String,
    },
  },
})

Props check their types at runtime, so you get a heads-up during development. And assuming you’re using TypeScript, which you should be, there are several tools to do so statically as well (Vetur’s interpolation feature).

Some components interact with the user to produce interactions on behalf of their parent. A prop containing a callback function can be used to give the component’s parent the ability to customize the processing. Here’s an example button component doing exactly that:

<template>
  <button @click="click">
    Click me!
  </button>
</template>
export default Vue.extend({
  props: {
    click: {
      type: Function as PropType<() => Promise<void>>,
    },
  },
})

Furthermore, such callbacks can be made to return promises, such that the child components can appropriately render a “loading” or “in progress” state when required to do so. Lastly, promises can also fail, giving the caller a chance to handle an error with a local display before a more global error handler takes over.

This is great. Flexible. Dry. What more would we want?

No, literally. We do not want anything else. And yet, there is something else, and its called an event.

Events are basically pseudo-callback props with literally no benefits of actual callback props. Look at this event-based button implementation.

<template>
  <button @click="click">
    Click me!
  </button>
</template>
export default Vue.extend({
  methods: {
    click() {
      this.$emit('click')
    }
  }
})

The magic event-emitting function $emit returns void and cannot throw errors. You cannot track an in-progress state, nor respond to errors/successes locally in this component. Many static analysis tools cannot pick up event names and arguments, as they’re basically just random function calls inside your component.

It should have been callback props from the bottom up, and Vue got it wrong.

Stop. Using. Events.