The component emits option
In Vue.js, the emits
option is used to explicitly declare the events that a component can trigger. It helps developers better understand the component's communication methods while providing type checking and improved documentation support.
Basic Usage of the emits Option
The emits
option can be an array or an object. When using an array, simply list the names of events the component may trigger:
export default {
emits: ['submit', 'cancel'],
methods: {
handleSubmit() {
this.$emit('submit', { data: 'some data' })
}
}
}
When using the object form, you can add validation functions for each event:
export default {
emits: {
submit: (payload) => {
return typeof payload === 'object' && 'data' in payload
}
},
methods: {
handleSubmit() {
this.$emit('submit', { data: 'valid payload' }) // Passes validation
// this.$emit('submit', 'invalid') // Will show a warning in the console
}
}
}
Why Use the emits Option
- Better Self-Documentation: By examining the
emits
option, other developers can immediately see which events the component will trigger. - Type Checking: In TypeScript projects,
emits
can work with the type system. - Runtime Validation: The object form of
emits
can validate whether event parameters meet expectations. - Vue 3 Compatibility: In Vue 3, events not declared in
emits
will be treated as native events bound to the component's root element.
Working with props
emits
is often used in conjunction with props
to achieve two-way communication between parent and child components:
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {
updateValue(newValue) {
this.$emit('update:modelValue', newValue)
}
}
}
Type Support in TypeScript
In TypeScript, you can more precisely define the types of event payloads:
interface SubmitPayload {
data: string
timestamp: number
}
export default defineComponent({
emits: {
submit: (payload: SubmitPayload) => {
return typeof payload.data === 'string' &&
typeof payload.timestamp === 'number'
}
},
methods: {
triggerSubmit() {
this.$emit('submit', {
data: 'example',
timestamp: Date.now()
})
}
}
})
Naming Conventions for Custom Events
While Vue doesn't enforce this, it's recommended to follow these conventions:
- Use kebab-case for event names (e.g.,
update-value
). - Use the
update:
prefix for two-way binding events. - Avoid naming conflicts with native DOM events.
export default {
emits: ['update:value', 'input-change'],
methods: {
handleInput(e) {
this.$emit('update:value', e.target.value)
this.$emit('input-change', e)
}
}
}
Advanced Usage of Event Validation
Validation functions can perform complex logic checks:
export default {
emits: {
upload: ({ file, size }) => {
const isValidType = ['image/jpeg', 'image/png'].includes(file.type)
const isValidSize = size < 1024 * 1024 // 1MB
return isValidType && isValidSize
}
},
methods: {
handleUpload(file) {
if (this.$emit('upload', { file, size: file.size })) {
// Validation passed, proceed with upload
}
}
}
}
Integration with the Composition API
Using emits
in the setup
function:
import { defineComponent } from 'vue'
export default defineComponent({
emits: ['focus', 'submit'],
setup(props, { emit }) {
const handleFocus = () => {
emit('focus', { focused: true })
}
return { handleFocus }
}
})
Handling Undeclared Events
In Vue 3, undeclared emits
behave differently:
export default {
// No emits declared
methods: {
triggerEvent() {
this.$emit('custom-event') // Treated as a native DOM event
this.$emit('click') // Will trigger twice (once native, once custom)
}
}
}
Performance Considerations
While emits
validation adds slight runtime overhead, it's very useful in development environments. These validation codes are removed in production builds.
Deep Integration with v-model
Vue 3 supports multiple v-model
bindings, where emits
plays a key role:
export default {
props: {
firstName: String,
lastName: String
},
emits: ['update:firstName', 'update:lastName'],
methods: {
updateName(type, value) {
this.$emit(`update:${type}`, value)
}
}
}
When using the component:
<NameComponent
v-model:first-name="firstName"
v-model:last-name="lastName"
/>
Event Validation in Testing
In component tests, you can verify whether events are correctly triggered:
import { mount } from '@vue/test-utils'
test('emits submit event', () => {
const wrapper = mount(MyComponent)
wrapper.vm.$emit('submit', { data: 'test' })
expect(wrapper.emitted().submit[0]).toEqual([{ data: 'test' }])
})
Integration with Third-Party Libraries
When integrating third-party libraries, emits
can help encapsulate native events:
export default {
emits: ['change'],
mounted() {
this.thirdPartyLib.on('change', (value) => {
this.$emit('change', value)
})
}
}
Alternative to the Event Bus Pattern
Although Vue 3 removed the global event bus, emits
can facilitate component communication:
// Parent component
const app = createApp({
template: `
<child-component @notify="handleNotify" />
`,
methods: {
handleNotify(message) {
console.log(message)
}
}
})
// Child component
app.component('child-component', {
emits: ['notify'],
created() {
this.$emit('notify', 'Hello from child')
}
})
Dynamic Event Names
In some advanced scenarios, you might need to dynamically generate event names:
export default {
emits: ['dynamic-event'],
methods: {
triggerDynamicEvent(eventName) {
if (this.$options.emits.includes(eventName)) {
this.$emit(eventName, 'payload')
}
}
}
}
Native Browser Events vs. Custom Events
It's important to understand the difference between native DOM events and Vue custom events:
export default {
emits: ['click'], // Custom click event
template: `
<button @click="$emit('click', $event)">
This button triggers both native and custom click events
</button>
`
}
Destructuring Event Parameters
You can destructure event parameters when receiving them:
// Parent component
<child-component @submit="handleSubmit" />
methods: {
handleSubmit({ data, meta }) {
console.log(data, meta)
}
}
// Child component
export default {
emits: ['submit'],
methods: {
triggerSubmit() {
this.$emit('submit', {
data: 'payload',
meta: { timestamp: Date.now() }
})
}
}
}
Combining Events with Slots
Sometimes you need to pass events through slots:
// Parent component
<template v-slot:item="{ data, emitEvent }">
<button @click="emitEvent('select', data)">
{{ data.name }}
</button>
</template>
// Child component
export default {
emits: ['select'],
template: `
<ul>
<li v-for="item in items" :key="item.id">
<slot name="item" :data="item" :emitEvent="$emit" />
</li>
</ul>
`
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn