Options emits Example

In Vue 3, the emits option plays a crucial role in explicitly defining the custom events a component can dispatch to its parent. By registering these events upfront, developers can establish clear component interfaces, validate event payloads, and prevent ambiguities caused by native event fallback behavior. This guide explores the two syntax styles of the emits option alongside practical implementation examples.
The most straightforward way to declare emitted events is using an array of strings. Each string represents the name of an event that the component is permitted to trigger:

export default {
  emits: ['submit', 'cancel']
}

Once declared, parent components can easily listen to these custom events using standard directives like v-on:

<template>
  <MyForm @submit="onSubmit" @cancel="onCancel" />
</template>

The Object Syntax: Payload Validation

When your component requires validation for the data passed along with an event, you can switch to the object syntax. In this format, each property key corresponds to an event name, and its value is either null or a validation function. The validation function receives the arguments passed to $emit and must return a boolean value indicating whether the payload is valid:

export default {
  emits: {
    // No validation required
    change: null,
    // With custom validation logic
    submit: (payload) => {
      if (payload && payload.email && payload.email.includes('@')) {
        return true
      }
      console.warn('The submit event payload must contain a valid email field.')
      return false
    }
  },
  methods: {
    handleSubmit() {
      this.$emit('submit', { email: 'user@example.com' })
    }
  }
}

It is important to note that if a validation function returns false, Vue will only emit a warning in the console; it will not block the event itself. Therefore, validation is primarily designed for development-time debugging and warning rather than strict runtime flow control.

Why Explicitly Declare Emits?

  1. Eliminate Fallthrough Confusion: Any event listeners not declared in emits are treated as native root-element listeners and fall through. For example, if a child component triggers a custom click event without declaring it, a parent's @click listener might accidentally bind twice to the root element.
  2. Self-Documenting Code: Explicit declarations allow other developers to instantly understand a component's outward-facing API without digging deep into the implementation methods.
  3. Enhanced TypeScript Support: Proper event declaration significantly improves type inference and autocomplete for payloads when using TypeScript.

A Complete Example

Here is a practical implementation of a counter button component utilizing validation:

<!-- CountButton.vue -->
<script>
export default {
  emits: {
    change: (count) => typeof count === 'number'
  },
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
      this.$emit('change', this.count)
    }
  }
}
</script>

<template>
  <button @click="increment">Count: {{ count }}</button>
</template>

And here is how the parent component consumes it:

<script>
import CountButton from './CountButton.vue'

export default {
  components: { CountButton },
  methods: {
    handleChange(count) {
      console.log('Counter updated to:', count)
    }
  }
}
</script>

<template>
  <CountButton @change="handleChange" />
</template>

When the user interacts with the button, the child component dispatches the change event with the latest count. The parent component's handleChange method then executes, smoothly capturing the updated numeric value.

Best Practices and Considerations

  • Naming Conventions: It is recommended to use kebab-case for event names (such as update:value) to maintain consistency across templates and scripts.
  • Model Updates: Events relied upon for v-model bindings, like update:modelValue, must also be included in the emits option.
  • Pure Validators: Ensure that validation functions remain pure and free of side effects.

Embracing the emits option leads to robust, predictable component contracts, helping developers minimize hidden bugs and build maintainable applications in Vue 3.