Emits and defineEmits
Props carry data down from parent to child. When a child needs to communicate back up — “the user clicked this person”, “the form was submitted” — it emits an event. The parent listens and responds.
Declaring emits with defineEmits
Section titled “Declaring emits with defineEmits”defineEmits is a compiler macro that declares the events a component can emit, with TypeScript types for their payloads:
<script setup lang="ts">const props = defineProps<{ id: string name: string clickable?: boolean}>()
const emit = defineEmits<{ select: [id: string]}>()
function handleClick() { if (props.clickable) { emit('select', props.id) }}</script>
<template> <div class="person-card" :class="{ clickable }" @click="handleClick" > {{ name }} </div></template>emit('select', props.id) fires the select event with the person’s id as the payload.
Listening to events in the parent
Section titled “Listening to events in the parent”In the parent, use v-on (or @) to listen:
<script setup lang="ts">import PersonCard from '../PersonCard/PersonCard.vue'
function handleSelect(id: string) { console.log('Selected person:', id) // update focus, navigate, etc.}</script>
<template> <PersonCard id="abc-123" name="Alice" :clickable="true" @select="handleSelect" /></template>@select="handleSelect" — when PersonCard emits select, Vue calls handleSelect with the payload. TypeScript knows id is a string because of the defineEmits declaration.
Inline handlers
Section titled “Inline handlers”For simple cases, the handler can be inline:
<PersonCard :id="person.id" :name="person.name" :clickable="true" @select="(id) => store.setFocus(id)"/>The event naming convention
Section titled “The event naming convention”Vue uses kebab-case for event names in templates: @select, @form-submit, @date-changed. In defineEmits, use camelCase: formSubmit — Vue handles the conversion automatically.
Why not just mutate a prop?
Section titled “Why not just mutate a prop?”The parent owns the data. The child emits intent (“user selected this”). The parent decides what to do with that intent. This separation makes components predictable and reusable — the same PersonCard can be used in different contexts where clicking means different things.
Exercise
Section titled “Exercise”- Add an
idprop toPersonCard.vue. - Add a
selectemit that fires with theidpayload when the card is clicked andclickableis true. - In
App.vue, render threePersonCardinstances with different ids and a@selecthandler that logs the selected id. - Verify in the browser console that clicking a card logs its id.
defineEmits<{ eventName: [payload type] }>()declares typed events a component can emit.emit('eventName', payload)fires the event from the child.- The parent listens with
@event-name="handler"— the handler receives the payload. - Props go down; events go up — this one-way flow keeps components predictable.