логотип PurpleSchool
логотип PurpleSchool

Гайд по defineEmits на Vue 3

Автор

Олег Марков

Введение

В современном Vue 3 одной из важных особенностей SFC (Single File Component) становится Composition API. Он предоставляет всевозможные инструменты для создания гибких реактивных интерфейсов, и одним из ключевых инструментов становится функция defineEmits. Она помогает удобно, безопасно и явно определять события, которые ваш компонент может отправлять наружу. Такой подход заметно улучшает поддержку TypeScript, читабельность кода и контроль за контрактами компонентов.

В этой статье вы разберетесь, как именно работает defineEmits, как правильно объявлять ваши события, прокидывать данные, оформлять типы, а также как использовать дополнительные возможности этого API в рамках вашего проекта.


Определение событий: зачем это нужно

Когда вы создаете компоненты во Vue, часто возникает необходимость «сообщить» родителю о каком-либо изменении. Для этого используются события (events). Раньше для объявления событий приходилось использовать опцию emits в опциях компонента или просто вызывать this.$emit внутри опций. С появлением Composition API и появился удобный способ явно и декларативно объявлять события через defineEmits.

Это решение решает сразу несколько задач:

  • Дает четкую документацию — видно, какие события компонент может сгенерировать.
  • Добавляет типизацию (особенно важно для TypeScript).
  • Защищает от опечаток в именах событий.
  • Улучшает автокомплит и интроспекцию в IDE.

Как работает defineEmits

В SFC с вы объявляете свои события через defineEmits. Эта функция возвращает функцию, с помощью которой вы впоследствии можете генерировать события. В самой сути подхода — это декларативное описание возможных событий и сигнатур их payload'ов.

Смотрите, как это выглядит в простейшем примере:

<script setup>
// Здесь мы объявляем, что наш компонент может отправлять событие "increment"
const emit = defineEmits(['increment'])

// Функция вызывается при клике, срабатывает событие
function onClick() {
  // Генерирует событие "increment" без параметров
  emit('increment')
}
</script>

<template>
  <button @click="onClick">Прибавить</button>
</template>

В этом коде очевидно, что компонент способен сгенерировать только событие increment.


Типизация событий с defineEmits

Особенно удобно defineEmits работает с TypeScript, позволяя детально описать payload ваших событий. Таким образом, если кто-то попытается вызвать событие несуществующем названием или передаст некорректные параметры, вы получите ошибку еще на этапе компиляции.

Давайте рассмотрим, как типизируется defineEmits — на примере:

<script setup lang="ts">
// Определяем события с помощью сигнатуры объекта
const emit = defineEmits<{
  (e: 'add', value: number): void
  (e: 'remove', id: string): void
}>()

function addItem() {
  emit('add', 10) // Верно
  // emit('add', 'строка') // Тут TypeScript скажет об ошибке!
}

function removeItem() {
  emit('remove', 'item42') // Верно
  // emit('remove', 100) // Тут тоже ошибка по типу!
}
</script>

Обратите внимание: с помощью TypeScript сигнатуры вы четко определяете, какие события существуют и какие параметры должны (или не должны) быть переданы.


Сравнение: declareEmits vs defineEmits в

В обычном SFC без <script setup> раньше события определяли через опцию emits объекта компонента:

export default {
  emits: ['save', 'delete']
}

Теперь в <script setup> вам следует использовать defineEmits. Это короче и проще, к тому же интегрировано в Composition API и поддерживает реальную типизацию через TS.


Варианты объявлений events через defineEmits

Через массив названий

Это самый простой способ — просто укажите список событий, которые компонент может генерировать:

<script setup>
const emit = defineEmits(['open', 'close'])
</script>

Через объект с валидаторами

Иногда важно не только объявить событие, но и удостовериться, что оно вызывается корректно с правильными параметрами (особенно для JS, без TS).

Vue поддерживает для defineEmits объектную форму, где ключ — название события, а значение — валидатор (функция возвращает bool):

<script setup>
const emit = defineEmits({
  submit: (payload) => {
    // здесь payload — то, что передается в emit
    // например, проверяем, что приходит число
    return typeof payload === 'number'
  },
  clear: null // событие без параметров
})
</script>

Если валидатор вернет false, Vue при dev-сборке выдаст предупреждение.

Через сигнатуры TypeScript

Этот способ вы уже видели — он обеспечивает наилучшую интеграцию с автокомплитом и строгой типизацией.

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'save', data: { id: number, text: string }): void
  (e: 'cancel'): void
}>()
</script>

Как использовать emit внутри компонента

После вызова defineEmits, вы получаете функцию emit. Просто вызывайте ее для отправки события.

Вот базовый пример использования emit с передачей данных:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'submit', value: string): void
}>()

function handleSubmit() {
  emit('submit', 'hello world') // Здесь мы отправляем событие и строку
}
</script>

<template>
  <button @click="handleSubmit">Отправить</button>
</template>

Передача событий родителям

Когда компонент отправляет событие, родитель может подписаться на это событие с помощью синтаксиса v-on (или просто через @):

<MyButton @submit="onSubmit" />

В данном случае родитель будет вызывать свой обработчик в ответ на emit('submit', ...).


Применение defineEmits в реальных сценариях

1. Классический компонент-контрол

Часто встречающийся паттерн — элемент управления (например, кастомный input или select), который сообщает родителю о изменениях значения:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void
}>()
// Используем двойное связывание через v-model
defineProps<{ modelValue: string }>()

function onInput(event: Event) {
  emit('update:modelValue', (event.target as HTMLInputElement).value)
}
</script>

<template>
  <input :value="modelValue" @input="onInput" />
</template>

Родительский компонент сможет использовать v-model:

<CustomInput v-model="username" />

В этом примере видна тесная интеграция defineEmits с паттернами двусторонней привязки.


2. Диалоговое окно с кастомными событиями

Можно организовать взаимодействие между диалогом и родителем:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'confirm', result: boolean): void
  (e: 'cancel'): void
}>()

function confirmDialog() {
  emit('confirm', true)
}

function cancelDialog() {
  emit('cancel')
}
</script>

<template>
  <button @click="confirmDialog">OK</button>
  <button @click="cancelDialog">Отмена</button>
</template>

3. Прием и переотправка событий (event forwarding)

Иногда дочерний компонент просто «перекидывает» событие родителю, возможно, чуть поменяв параметры или имя:

<script setup lang="ts">
const emit = defineEmits<{
  (e: 'submit', data: string): void
}>()

function onChildSubmit(data: string) {
  emit('submit', data)
}
</script>

<template>
  <ChildComponent @submit="onChildSubmit" />
</template>

Особенности и ограничения defineEmits

  • Работает только внутри <script setup> — если вы используете обычный скрипт, работает только emits: в опциях.
  • Не поддерживает динамические имена событий — TypeScript проверяет только явно объявленные имена.
  • Не контролирует правильность имен событий на стороне родителя — если родитель слушает несуществующее событие, это не приведет к ошибке, просто не произойдет вызов.
  • Валидаторы в объектной форме не работают в production — проверки происходят только в режиме разработки.

Лайфхаки и лучшие практики

Используйте типы для сложных событий

Если событие передает сложный объект, вынести его структуру в отдельный интерфейс:

interface UserData {
  id: number
  name: string
}

const emit = defineEmits<{
  (e: 'save', user: UserData): void
}>()

Явно именуйте события

Если событие связано с изменением значения, используйте паттерн update:modelValue, чтобы v-model корректно работал с вашим компонентом.

Не злоупотребляйте emit

Переходите к useExpose или provide/inject, если компонент начинает передавать слишком много разных событий.

Проверяйте корректность payload в рантайме

TypeScript не гарантирует вам правильную работу в runtime. Для сложных payload, если есть подозрение на внешний ввод, стоит добавить защиту — например, использовать валидаторы.


Заключение

Vue 3 с появлением defineEmits в делает API для событий максимально прозрачным, предсказуемым и типобезопасным. Вы теперь явно видите, какие события способны исходить из компонента, а ваши коллеги благодаря этому быстрее читают код и могут не бояться рефакторинга. Интеграция с TypeScript позволяет сделать dev-опыт еще приятнее и безопаснее, а объектная форма с валидаторами пригодится в JS-проектах. Следуйте лучшим практикам и пользуйтесь defineEmits в новых компонентах для надежной работы со событиями.


Частозадаваемые технические вопросы по теме defineEmits

Как пробросить все события дочернего компонента напрямую во внешний компонент?

Вы можете использовать $attrs и v-bind, чтобы пробросить переданные родителю события, не объявляя их по-отдельности:

<ChildComponent v-bind="$attrs" />

Расшифровка: так вы все входящие слушатели (например, @click, @update:modelValue) переадресуете на дочерний элемент.

Можно ли динамически определять список событий в defineEmits?

Нет, объявления событий в defineEmits статичны. Для динамических сценариев используйте массив или объектную форму, однако сигнатуры в TS строго фиксированы.

Как добавить описание события для автогенерации документации?

Сам по себе defineEmits не поддерживает описательные комментарии к событиям. Для автогенерации документации используйте jsdoc выше вызова defineEmits:

/**
 * @event save - Сохраняет результат
 */
const emit = defineEmits(['save'])

И обрабатывайте с помощью сторонних инструментов.

Как быть с событиями, которые должен прокидывать mixin или composable в ?

Вызывайте defineEmits в каждом компоненте, где происходит emit. Если выносите обработку в composable, передавайте emit как аргумент:

export function useStuff(emit: ReturnType<typeof defineEmits>) {
  // ...
}

Почему TypeScript не ругается на отсутствующее событие при использовании emit('unknownEvent')?

Это возможно лишь если вы указали форму без типизации (массив/объект без TS). Для строгой типизации пишите сигнатуру через TS, тогда все несуществующие события вызовут ошибку типов на этапе сборки.

Стрелочка влевоОбработка событий и их передача между компонентами VuejsПонимание core функционала Vue и его применениеСтрелочка вправо

Все гайды по Vue

Руководство по валидации форм во Vue.jsИнтеграция Tiptap для создания редакторов на VueРабота с таблицами во Vue через TanStackИнструкция по установке и компонентам Vue sliderУправление пакетами Vue js с помощью npmУправление пакетами и node modules в Vue проектахКак использовать meta для улучшения SEO на VueПолный гайд по компоненту messages во Vuejs5 правил использования Inertia с Vue и LaravelРабота с модулями и пакетами в VueИнструкция по работе с grid на VueGithub для Vue проектов - подробная инструкция по хранению и совместной работеНастройка ESLint для Vue проектов и поддержка качества кодаОбработка ошибок и отладка в Vue.jsИспользование Vue Devtools для отладки и мониторинга приложенийРабота с конфигурационными файлами и скриптами VueСоздание и настройка проектов Vue с помощью Vue CLI3 способа интеграции Chart.js с Vue для создания графиковРабота с Canvas во VueИнструкция по реализации календаря во VueРабота с Ant Design Vue для создания UI на Vue
Обзор и использование утилит Vue для удобной разработкиРабота с обновлениями компонента и жизненным циклом updateИспользование query-параметров и их обработка в маршрутах VueРазрешение конфликтов и ошибок с помощью Vue resolveЗагрузка и управление состоянием загрузки в VueИспользование библиотек Vue для расширения функционалаРабота с JSON данными в приложениях VueКак работать с экземплярами компонента Instance во VueПолучение данных и API-запросы во Vue.jsЭкспорт и импорт данных и компонентов в VueОбработка событий и их передача между компонентами VuejsГайд по defineEmits на Vue 3Понимание core функционала Vue и его применениеПонимание и применение Composition API в Vue 3Понимание и работа с компилятором VueКогда и как использовать $emit и call во VueВзаимодействие с внешними API через Axios в Vue
Веб приложения на Vue архитектура и лучшие практикиИспользование Vite для быстрого старта и сборки проектов на Vue 3Работа с URL и ссылками в приложениях на VueРабота с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueИспользование Quasar Framework для разработки на Vue с готовыми UI-компонентамиОбзор популярных шаблонов и стартовых проектов на VueИнтеграция Vue с PHP для создания динамичных веб-приложенийКак организовать страницы и маршруты в проекте на VueNuxt JS и Vue 3 для SSR приложенийСоздание серверных приложений на Vue с помощью Nuxt jsИспользование Vue Native для разработки мобильных приложенийОрганизация и управление индексной страницей в проектах VueИспользование Docker для контейнеризации приложений на VueИнтеграция Vue.js с Django для создания полноценных веб-приложенийСоздание и работа с дистрибутивом build dist Vue приложенийРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияКак исправить ошибку cannot find module vueНастройка и сборка проектов Vue с использованием современных инструментовИнтеграция Vue с Bitrix для корпоративных решенийРазработка административных панелей на Vue js
5 библиотек для создания tree view во VueИнтеграция Tailwind CSS с Vue для современных интерфейсовИнтеграция Vue с серверной частью и HTTPS настройкамиКак обрабатывать async операции с Promise во VueИнтеграция Node.js и Vue.js для разработки приложенийРуководство по интеграции Vue js в NET проектыПримеры использования JSX во VueГайд по импорту и регистрации компонентов на VueМногоязычные приложения на Vue с i18nИнтеграция FLIR данных с Vue5 примеров использования filter во Vue для упрощения разработки3 примера реализации drag-and-drop во Vue
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueПрименение v-bind для динамической привязки атрибутов в VueУправление пользователями и их данными в Vue приложенияхСоздание и использование UI Kit для Vue приложенийТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовРабота со стилями и стилизацией в VueИспользование Swiper для создания слайдеров в VueСтруктура и особенности Single File Components SFC в VueРабота со SCSS в проектах на Vue для стилизацииРабота со скроллингом и прокруткой в Vue приложенияхПрименение script setup синтаксиса в Vue 3 для упрощения компонентовИспользование scoped стилей для изоляции CSS в компонентах Vue3 способа улучшить навигацию Vue с push()Обработка запросов и асинхронных операций в VueПонимание и использование provide inject для передачи данных между компонентамиПередача и использование props в Vue 3 для взаимодействия компонентовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsРабота со свойствами компонентов VueУправление параметрами и динамическими данными во VueРабота с lifecycle-хуком onMounted во VueОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование модальных окон modal в Vue приложенияхИспользование методов в компонентах Vue для обработки логикиИспользование метода map в Vue для обработки массивовИспользование хуков жизненного цикла Vue для управления состоянием компонентаРабота с ключами key в списках и компонентах VueОбработка пользовательского ввода в Vue.jsРабота с изображениями и их оптимизация в VueИспользование хуков жизненного цикла в VueОрганизация сеток и гридов для верстки интерфейсов на VueСоздание и управление формами в VueОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitРабота с динамическими компонентами и данными в Vue3 способа манипулирования DOM на VueРуководство по div во VueИспользование директив в Vue и их расширенные возможностиОсновы и применение директив в VueИспользование директив и их особенности на Vue с помощью defineИспользование компонентов datepicker в Vue для выбора датОрганизация циклов и итераций во VueКак работает компиляция Vue CoreСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в VueИспользование классов в Vue для организации кода и компонентовИспользование директивы checked для управления состоянием чекбоксов в VueГайд на checkbox компонент во VueОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в VueСоздание и настройка кнопок в Vue приложенияхРабота с lifecycle-хуками beforeCreate и beforeMount во VueИспользование массивов и методов их обработки в VueИспользование массивов и их обработка в Vue
Использование Vuetify для создания современных интерфейсов на VueИспользование transition во VueТестирование компонентов и приложений на VueРабота с teleport для управления DOM во VueПять шагов по настройке SSR в VuejsИспользование Shadcn UI компонентов с Vue для продвинутых интерфейсовИспользование router-link для навигации в Vue RouterКак использовать require в Vue для динамического импорта модулейРабота с динамическим рендерингом и виртуальным DOM на Vue.jsИспользование ref для управления ссылками и реактивностью в Vue 3Использование Vue Pro и его преимущества для профессиональной разработкиРуководство по nextTick для работы с DOMСоздание и использование компонентов с помощью Vue js и CУправление состоянием и реактивностью через inject и provideДинамическое обновление компонентов и данных на VueГлубокое изучение документации Vue и как эффективно её использоватьИспользование Crystal с Vue для разработкиИспользование вычисляемых свойств для динамического отображения данных на Vue jsОптимизация производительности и предупреждения в Vue
Открыть базу знаний