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?
- Eliminate Fallthrough Confusion: Any event listeners not declared in
emitsare 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@clicklistener might accidentally bind twice to the root element. - Self-Documenting Code: Explicit declarations allow other developers to instantly understand a component's outward-facing API without digging deep into the implementation methods.
- 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-casefor event names (such asupdate:value) to maintain consistency across templates and scripts. - Model Updates: Events relied upon for
v-modelbindings, likeupdate:modelValue, must also be included in theemitsoption. - 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.