Vue: $emit и defineEmits для отправки событий от дочерних компонентов

Компонентная архитектура упрощает создание и поддержку веб‑приложений. Внутри такой архитектуры компоненты общаются разными способами: props, слоты и пользовательские события. Пользовательские события позволяют передать данные от дочернего компонента к родительскому, когда нужно сообщить о действии или отправить результат.
Почему стоит использовать события из дочерних компонентов
Коротко: props идут сверху вниз, события идут снизу вверх. Это явная и простая модель взаимодействия. Поддерживаемость и читаемость кода улучшаются, если роли компонентов предельно ясны: родитель выдаёт данные через props, а дочерний сообщает изменения через события.
Важно: это не всегда единственный вариант. Для более глубокого обмена состоянием используйте provide/inject или централизованные хранилища вроде Pinia.
Именование пользовательских событий
Рекомендации по именованию:
- В шаблонах HTML используйте kebab-case, например my-event или button-clicked.
- В JavaScript допустим camelCase, но Vue в итоге приводят имена к kebab-case при привязке в шаблонах.
- Выбирайте описательные имена, которые сразу показывают цель события, например item-added, form-submitted, validation-error.
Хелпер: думайте о событии как о глаголе. Название должно отвечать на вопрос Что произошло?
Два способа эмитить события в Vue
Есть два распространённых подхода:
- $emit — встроенный метод, удобно вызывать прямо в шаблоне или в опциях компонента.
- defineEmits — макрос composition API, который явно описывает набор событий и возвращает функцию для их отправки.
Ниже приведены оба подхода с рабочими примерами.
$emit: пример дочернего компонента
Пояснения:
- post — локальная реактивная переменная, связанная с полем ввода через v-model.
- При клике вызывается $emit с двумя аргументами: имя события и полезная нагрузка post.
- Родитель может прослушать это событие и получить значение post.
Родитель, который принимает событие через @
- {{ post }}
Здесь родитель регистрирует слушатель события @button-clicked и вызывает addPosts с переданным значением.
defineEmits: пример с явным описанием событий
Преимущества defineEmits:
- Явно видно, какие события может излучать компонент.
- Удобно для типов и автодополнения в IDE.
- Поддерживает более структурированный подход в composition API.
Когда это подходит, а когда нет
Когда использовать события:
- Простая передача данных один уровень вверх.
- Сигнализация об одноразовом действии, например submit, click, delete.
- Поддержка модульности и слабой связанности компонентов.
Когда события не подходят:
- Когда нужно синхронизировать состояние между многими компонентами в разных частях дерева. В этом случае лучше использовать provide/inject или централизованное хранилище.
- Когда требуется двунаправленная синхронизация сложных структур данных. Используйте родительское состояние и props или управляйте состоянием через store.
- Для передачи команд между не родственными компонентами рассмотрите event bus или глобальное состояние, но помните о рисках утечки памяти и отладки.
Альтернативные подходы
- Props вниз и события вверх — стандартная модель, хороша для большинства случаев.
- provide / inject — для передачи зависимостей глубоко вниз без пробрасывания через промежуточные компоненты.
- Pinia / Vuex — централизованное состояние для сложных приложений.
- Компонент‑событийная шина (event bus) — устаревающий подход, полезен в небольших проектах, но усложняет трассировку событий.
Мини‑методология: как внедрить пользовательские события правильно
- Определите границы ответственности компонента. Если компонент управляет своей локальной формой, он должен эмитить событие submit или change.
- Назовите событие описательно в kebab-case для шаблонов.
- В composition API используйте defineEmits для явности и типизации.
- В родителе обрабатывайте событие и делайте минимальную работу: обновление состояния или делегирование дальше.
- Покройте сценарии тестами и добавьте критерии приёмки.
Критерии приёмки
- Компонент должен эмитить событие с ожидаемым именем при соответствующем действии.
- Родитель корректно принимает полезную нагрузку и обновляет состояние.
- Имя события понятно и соответствует документации компонента.
- Не должно быть утечек подписок или глобальных слушателей, оставшихся после уничтожения компонента.
Чек‑лист для ролей
Разработчик:
- Разработать компонент с явными событиями через defineEmits.
- Написать unit‑тесты, которые проверяют эмиссию событий.
- Документировать событие и формат полезной нагрузки.
Код‑ревьювер:
- Проверить имена событий на описательность.
- Убедиться, что нет излишней логики в обработчике события в дочернем компоненте.
- Проверить, что parent делает минимальную работу и не дублирует логику.
Тестировщик:
- Проверить UI‑сценарии, где событие вызывает обновление.
- Проверить сценарии с пустой полезной нагрузкой и неверными типами.
Примеры тестовых случаев и критерии приёмки
- При клике по кнопке дочернего компонента с заполненным полем input эмитится событие button-clicked с корректной строкой.
- Если поле input пустое, событие либо не эмитится, либо эмитится с пустой строкой в зависимости от спецификации.
- Родитель добавляет новый элемент в список и рендерит его.
Ментальные модели и эвристики
- Правило двух направлений: props — вниз, events — вверх. Это базовая мемориальная модель.
- Модель единственного источника правды: любое состояние, изменяемое несколькими компонентами, лучше вынести в общий источник.
- Ясность ответственности: компонент отвечает за отправку сигнала, родитель — за обработку результата.
Контрпримеры: когда простые события ломаются
- Сильно вложенные компоненты, где событие должно пройти через много промежуточных уровней. Вызов события может стать источником багов, если промежуточные компоненты не пробрасывают слушатели.
- Когда нужно подтвердить действие на стороне сервера до обновления состояния. Тогда дочерний компонент должен эмитить событие, родитель — сделать запрос и обработать результат асинхронно.
Сопровождающая документация и совместимость
- Vue 3 сохраняет совместимость в том, как шаблоны связываются с событиями, но синтаксис defineEmits доступен только в composition API с