События компонента: emit
Данные идут от родителя к дочернему компоненту через props, а сообщения в обратную сторону — через события. Дочерний компонент генерирует (emit) событие, например «задачу нужно удалить», а родитель слушает его через @ так же, как клик по кнопке, и сам меняет данные.
Объявление событий
Добавьте в src/components/TodoItem.vue макрос defineEmits со списком событий:
<!-- src/components/TodoItem.vue -->
<script setup>
defineProps({
todo: { type: Object, required: true },
})
const emit = defineEmits(['remove'])
</script>
<template>
<li>
{{ todo.text }}
<button @click="emit('remove')">Удалить</button>
</li>
</template>Клик по кнопке вызывает emit('remove') — компонент сообщает наружу, что пользователь хочет удалить задачу. Сам он ничего не удаляет.
Обработка в родителе
<!-- src/App.vue -->
<script setup>
import { ref } from 'vue'
import TodoItem from './components/TodoItem.vue'
const todos = ref([
{ id: 1, text: 'Купить хлеб', done: false },
{ id: 2, text: 'Позвонить Анне', done: false },
])
function removeTodo(id) {
todos.value = todos.value.filter((todo) => todo.id !== id)
}
</script>
<template>
<ul>
<TodoItem
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@remove="removeTodo(todo.id)"
/>
</ul>
</template>Кнопка «Удалить» в каждой строке убирает свою задачу. Родитель знает id из цикла и передаёт его в функцию.
Данные вместе с событием
Вторым аргументом emit можно передать значение. Добавим событие toggle, которое сообщает новое состояние флажка:
<!-- src/components/TodoItem.vue -->
<script setup>
defineProps({
todo: { type: Object, required: true },
})
const emit = defineEmits(['remove', 'toggle'])
</script>
<template>
<li>
<input
type="checkbox"
:checked="todo.done"
@change="emit('toggle', $event.target.checked)"
>
{{ todo.text }}
<button @click="emit('remove')">Удалить</button>
</li>
</template><!-- src/App.vue, фрагмент шаблона -->
<TodoItem
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@toggle="(checked) => (todo.done = checked)"
@remove="removeTodo(todo.id)"
/>Отметка флажка вызывает toggle с true или false, и родитель записывает это значение в задачу.
Поток данных
| Направление | Механизм | Пример |
|---|---|---|
| Родитель → дочерний | props | :todo="todo" |
| Дочерний → родитель | emit | @remove="removeTodo(todo.id)" |
Такое правило — данные вниз, события вверх — делает понятным, кто и где меняет состояние.
Частая ошибка
Забыть объявить событие в defineEmits. Если список событий есть, но нового в нём нет, Vue выведет в консоль предупреждение. Если же defineEmits нет вовсе, слушатель из родителя попадёт на корневой элемент компонента как обычный обработчик DOM. Для события с именем click это значит, что обработчик сработает дважды: от emit и от настоящего клика. Объявляйте все события, которые генерирует компонент.