Chapter 92: Vue ’emits’ Option
The emits option
This is the official, clean, declarative way to tell Vue (and other developers reading your code):
“These are all the custom events that this component might emit to its parent.”
In other words: emits is your component’s public API contract for upward communication.
Before Vue 2.6 / Vue 3, there was no emits option → developers just called this.$emit(‘anything’) whenever they felt like it. That led to:
- Typos in event names (update-usre instead of update:user)
- No autocompletion / type safety
- No warning when parent listens to non-existent events
- Hard-to-maintain components (nobody knows what events exist)
Since Vue 2.6 (2019) and especially in Vue 3, the emits option became the recommended and eventually required way to declare events.
1. Two Main Forms of emits (Both still used in 2026)
A. Array form (simple, most common in small components)
|
0 1 2 3 4 5 6 |
emits: ['update', 'delete', 'close', 'confirm'] |
→ Just a list of event names → Vue will warn in development mode if you emit something not in the list
B. Object form (with validation & type hints – preferred in larger apps)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
emits: { // Simple event – no payload close: null, // Event with payload – type checking in dev update: (id, newValue) => { // Optional runtime validation (rarely used in 2026) return typeof id === 'number' && typeof newValue === 'string' }, // Modern TypeScript + defineEmits style (object form still valid) delete: (id) => typeof id === 'number' } |
In Vue 3 + TypeScript → almost nobody uses the object form anymore — they use defineEmits<…>() instead (see below).
2. Real, Complete Example – TodoItem with Multiple Emitted Events
Child: TodoItem.vue (Options API style – what you still see in legacy code)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
<template> <div class="todo-item" :class="{ completed: done }"> <input type="checkbox" :checked="done" @change="emitToggle" /> <span class="text">{{ text }}</span> <button class="delete-btn" @click="emitDelete"> × </button> </div> </template> <script> export default { name: 'TodoItem', // ── This is the emits option ─────────────────────────────── emits: [ 'toggle', 'delete' ], // Alternative object form (with optional validation) // emits: { // toggle: (id) => typeof id === 'number', // delete: (id) => typeof id === 'number' // }, props: { id: { type: Number, required: true }, text: { type: String, required: true }, done: { type: Boolean, default: false } }, methods: { emitToggle() { // Emit the event + payload this.$emit('toggle', this.id) }, emitDelete() { this.$emit('delete', this.id) } } } </script> <style scoped> .todo-item { display: flex; align-items: center; padding: 0.9rem 1.2rem; background: white; border-radius: 8px; margin-bottom: 0.6rem; box-shadow: 0 1px 4px rgba(0,0,0,0.08); } .completed .text { text-decoration: line-through; color: #9ca3af; } .delete-btn { background: none; border: none; color: #ef4444; font-size: 1.5rem; cursor: pointer; padding: 0 0.6rem; } </style> |
Parent – listening to emitted events
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
<template> <div class="todo-list"> <h2>My Todos</h2> <TodoItem v-for="todo in todos" :key="todo.id" :id="todo.id" :text="todo.text" :done="todo.done" @toggle="toggleTodo" @delete="deleteTodo" /> <p>{{ remaining }} remaining</p> </div> </template> <script> export default { data() { return { todos: [ { id: 1, text: 'Learn emits option', done: true }, { id: 2, text: 'Master Vue 3', done: false } ] } }, computed: { remaining() { return this.todos.filter(t => !t.done).length } }, methods: { toggleTodo(id) { const todo = this.todos.find(t => t.id === id) if (todo) todo.done = !todo.done }, deleteTodo(id) { this.todos = this.todos.filter(t => t.id !== id) } } } </script> |
3. Modern Vue 3 – Why We Almost Never Use emits: [ … ] Anymore
In <script setup> (the 2026 standard):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<script setup lang="ts"> // Modern, type-safe, autocompletion const emit = defineEmits<{ (e: 'toggle', id: number): void (e: 'delete', id: number): void (e: 'update', id: number, newText: string): void }>() function emitToggle() { emit('toggle', props.id) } function emitDelete() { emit('delete', props.id) } </script> |
→ No emits object → No this.$emit → Full TypeScript support (event names + payload types) → Autocompletion in template & code → Vue warns if you emit undeclared event
4. Important Rules & Gotchas (2026 Must-Know)
| Rule / Gotcha | Correct Behavior / Best Practice |
|---|---|
| emits is optional (but strongly recommended) | Declare it — prevents typos & gives better Devtools/info |
| Event names should be kebab-case in template | @update:user in template → emit(‘update:user’) in code |
| v-model support | Emit ‘update:modelValue’ → enables <MyComp v-model=”value” /> |
| Multiple v-model | Emit ‘update:propName’ → enables v-model:title, v-model:body |
| Still used in 2026? | Yes — in legacy Options API code, some plugins, interviews |
| Modern replacement | defineEmits<…>() — type-safe & cleaner |
Quick Summary Table – emits in 2026
| Question | Options API (legacy) | Composition API (<script setup>) | What you should do in new code |
|---|---|---|---|
| How to declare events? | emits: [‘toggle’, ‘delete’] | defineEmits<…>() | Composition API |
| Emit an event | this.$emit(‘toggle’, id) | emit(‘toggle’, id) | — |
| TypeScript friendly? | Poor | Excellent (event names + payload types) | Use defineEmits |
| Still used in 2026? | Yes — legacy code, plugins, interviews | Almost never (except legacy) | Avoid unless maintaining old code |
Final 2026 Advice from Real Projects
- In new projects → never write emits: [ … ] — use defineEmits<…>() in <script setup>
- When you see emits as an array/object → it means Options API (legacy style)
- Learn to read Options API — many jobs, open-source projects, tutorials still use it
- Never teach beginners emits array as primary — start with defineEmits
- If migrating old code → gradually convert this.$emit → emit(), emits: […] → defineEmits<…>()
Your mini homework:
- Create the TodoItem component above in Options API
- Use it in parent with @toggle and @delete
- Try emitting a typo event → see Vue warn in console (if declared)
- Convert it to <script setup> with defineEmits<…>() → compare type safety
Any part confusing? Want to see:
- Full Options API vs Composition API emits comparison project?
- How emits looks in Vue Devtools?
- Common bugs when forgetting to declare events?
- Real component with multiple v-model using update:propName?
Just tell me — we’ll convert and compare together step by step 🚀
