Emits / Events
Vue emits: defineEmits, typed events, v-model integration, and the patterns for child-to-parent communication.
Vue — emits
EXAMPLE
<!-- ===== Define emits ===== -->
<!-- Child.vue -->
<script setup>
const emit = defineEmits(['save', 'cancel']);
function onSubmit() {
emit('save', { id: 1, name: 'Alex' });
}
</script>
<template>
<button @click="onSubmit">Save</button>
<button @click="emit('cancel')">Cancel</button>
</template>
<!-- Parent: -->
<Child @save="onSave" @cancel="onCancel" />
<!-- ===== Typed emits (TS) ===== -->
<script setup lang="ts">
const emit = defineEmits<{
(e: 'save', value: { id: number; name: string }): void;
(e: 'cancel'): void;
(e: 'change', count: number): void;
}>();
// Better: alternative tuple style
const emit2 = defineEmits<{
save: [value: { id: number; name: string }];
cancel: [];
change: [count: number];
}>();
</script>
<!-- ===== Validation ===== -->
<script setup>
const emit = defineEmits({
save(payload) {
if (!payload || typeof payload !== 'object') {
console.warn('save payload must be an object');
return false;
}
return true;
},
cancel: null, // no validation
});
</script>
<!-- ===== v-model emits update:modelValue ===== -->
<!-- Child.vue (custom input) -->
<script setup>
defineProps(['modelValue']);
const emit = defineEmits(['update:modelValue']);
</script>
<template>
<input :value="modelValue" @input="emit('update:modelValue', $event.target.value)" />
</template>
<!-- Parent: -->
<Child v-model="name" />
<!-- ===== Multiple v-models (Vue 3.4+) ===== -->
<!-- Child.vue -->
<script setup>
defineProps(['first', 'last']);
defineEmits(['update:first', 'update:last']);
</script>
<!-- Parent: -->
<Child v-model:first="firstName" v-model:last="lastName" />
<!-- ===== defineModel (Vue 3.4+, simpler) ===== -->
<script setup>
const modelValue = defineModel(); // automatic v-model binding
const first = defineModel('first');
const last = defineModel('last');
function uppercase() { first.value = first.value.toUpperCase(); }
</script>
<template>
<input v-model="modelValue" />
<input v-model="first" />
<input v-model="last" />
</template>
<!-- ===== When to emit vs use v-model =====
- v-model when the value is owned by the parent and shared with the child for editing
- emit for events that are 'something happened' but the parent owns no state directly
- Both can coexist (v-model + emit other actions)
-->
<!-- ===== Patterns to internalise =====
- Type emits with TS tuple syntax
- Use defineModel for simple two-way bindings (3.4+)
- Multiple v-models for compound inputs (date pickers)
- Validate payloads in dev to catch shape drift early
-->
<!-- ===== Pitfalls =====
- emit('changed') on every input keystroke -> debounce
- Mutating event payloads in the parent -> child may pass shared refs
- Type-mismatch between declared emits and actual usage (no runtime check)
- Forgetting that v-model translates to 'update:modelValue' under the hood
-->
Why it matters
Emits are child-to-parent communication. Define them with TS tuples, prefer defineModel for two-way bindings (3.4+), and use multiple v-models for compound controls. Pair with prop validation and the contract between components stays loud.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
<!-- Child -->
<script setup>
const emit = defineEmits(['save']);
function onClick() { emit('save', { id: 1 }); }
</script>
<!-- Parent -->
<Child @save="onSave" />
Try it Yourself »
Discussion
Loading…