Component events with defineEmits

Vue · Practical · 9 min

Events are the other half of the component contract: props go down, events go up.

A child talks back to its parent by emitting events. Declare them with the defineEmits macro, which returns an emit function:

const emit = defineEmits(['remove', 'rename'])
emit('rename', newName)

The parent listens exactly like a DOM event: <TodoItem @remove="deleteTodo(todo.id)" @rename="(name) => todo.name = name" />. Extra arguments passed to emit become the handler's arguments. Events emitted in camelCase (emit('itemSelected')) can be listened to as @item-selected.

In the template you can emit directly with $emit('remove').

With TypeScript, type the payloads with the named-tuple syntax:

const emit = defineEmits<{ rename: [name: string]; remove: [] }>()

Declaring events is not just documentation. Listeners for undeclared events fall through to the component's root element as native listeners (see attribute fallthrough), which can make a click fire twice. Declared events are also validated in dev and show up in devtools.

Component events do not bubble: only the direct parent can listen. For distant communication use provide/inject or a store instead of passing events up through many layers.

A useful convention: name events after what happened (select, close, update:modelValue), not after what the parent should do.

Example

<!-- RenameField.vue -->
<script setup>
import { ref } from 'vue'
const { initial } = defineProps({ initial: String })
const emit = defineEmits(['rename', 'cancel'])
const draft = ref(initial)
</script>
<template>
  <input v-model="draft" @keyup.enter="emit('rename', draft.trim())">
  <button @click="$emit('cancel')">Cancel</button>
</template>

<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import RenameField from './RenameField.vue'
const title = ref('Inbox')
const editing = ref(true)
function onRename(name) { title.value = name; editing.value = false }
</script>
<template>
  <RenameField v-if="editing" :initial="title"
               @rename="onRename" @cancel="editing = false" />
  <h2 v-else>{{ title }}</h2>
</template>

Remember

  • const emit = defineEmits([...]), then emit('name', payload)
  • Parents listen with @name; payload arguments go to the handler
  • Component events don't bubble; declare all emitted events

Watch out for

  • Emitting an undeclared click event while the root element also fires a native click, so the parent handler runs twice.

Check yourself

  1. A grandchild emits 'save'. Can the grandparent listen with @save on the parent component?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: vuejs.org/guide/components/events.html