События и эмиты между компонентами

Во Vue.js взаимодействие между компонентами строится по принципу однонаправленного потока данных: родитель передаёт данные вниз через props, а дочерний компонент сообщает о событиях вверх через механизм событий и эмитов. Такой подход упрощает архитектуру приложения, делает связи между компонентами явными и контролируемыми.

События используются для передачи факта произошедшего действия, а не данных состояния. Компонент не изменяет данные родителя напрямую, а уведомляет его о необходимости реакции.


Генерация событий с помощью $emit

Дочерний компонент может сгенерировать пользовательское событие, вызвав метод $emit. Первый аргумент — имя события, последующие аргументы — произвольные данные.

export default {
  methods: {
    increment() {
      this.$emit('increment', 1)
    }
  }
}

Здесь компонент сообщает о намерении увеличить значение, не зная, где и как это значение хранится.

Ключевые особенности:

  • имя события регистрозависимо в JavaScript, но в шаблонах используется kebab-case;
  • можно передавать любое количество аргументов;
  • событие существует только в момент эмита и не хранит состояние.

Прослушивание событий в родительском компоненте

Родительский компонент подписывается на событие через директиву v-on или сокращённую форму @.

<CounterButton @increment="handleIncrement" />
methods: {
  handleIncrement(value) {
    this.count += value
  }
}

Обработчик получает данные, переданные через $emit, и принимает решение о дальнейших действиях.


Именование событий

События должны описывать факт, а не действие обработчика. Хорошая практика — использовать глаголы в прошедшем времени или нейтральные формулировки.

Примеры корректных имён:

  • submit
  • update:modelValue
  • item-selected

Некорректные варианты:

  • changeParentData
  • setValue

Объявление эмитов в компоненте (emits)

Во Vue 3 появился явный способ описания событий через опцию emits. Это повышает читаемость и позволяет фреймворку выполнять дополнительные проверки.

export default {
  emits: ['increment'],
  methods: {
    increment() {
      this.$emit('increment', 1)
    }
  }
}

Вариант с валидацией аргументов:

export default {
  emits: {
    increment(value) {
      return typeof value === 'number'
    }
  }
}

Если валидация возвращает false, Vue выдаст предупреждение в режиме разработки.


Двустороннее связывание и событие update:modelValue

Механизм v-model основан на паре: prop + событие обновления. По умолчанию используется modelValue и событие update:modelValue.

<CustomInput v-model="text" />
export default {
  props: {
    modelValue: String
  },
  emits: ['update:modelValue'],
  methods: {
    onInput(event) {
      this.$emit('update:modelValue', event.target.value)
    }
  }
}

Таким образом компонент остаётся управляемым извне, а изменение значения происходит только через событие.


Пользовательские аргументы для v-model

Vue поддерживает несколько v-model с пользовательскими именами.

<RangeSelector v-model:start="min" v-model:end="max" />
export default {
  props: {
    start: Number,
    end: Number
  },
  emits: ['update:start', 'update:end']
}

Это позволяет строить сложные формы без нарушения принципов однонаправленного потока данных.


Передача событий через слои компонентов

События не всплывают автоматически через несколько уровней вложенности. Если компонент находится глубоко в дереве, возможны варианты:

Ручная прокладка событий

<Child @save="$emit('save')" />

Использование composables или глобального состояния — предпочтительный вариант для сложных сценариев.


Отличие событий от глобальной шины

Ранее во Vue 2 часто применялась глобальная event bus. Такой подход приводил к неявным зависимостям и трудностям отладки. В современных версиях Vue рекомендуется:

  • использовать события только между родителем и дочерним компонентом;
  • для межкомпонентного взаимодействия применять Pinia, provide/inject или composables.

Множественные обработчики одного события

На одно событие можно подписать несколько обработчиков, комбинируя логику:

<MyForm
  @submit="validate"
  @submit="save"
/>

Порядок вызова соответствует порядку объявления.


Модификаторы событий

Для нативных событий доступны модификаторы (.stop, .prevent), но для пользовательских событий они не применяются. Если требуется контролировать поведение, это делается внутри метода-эмиттера или обработчика.


Асинхронная логика и события

$emit не поддерживает ожидание результата. Событие — уведомление, а не запрос. Если требуется вернуть результат:

  • передаётся callback-функция;
  • используется Promise через prop;
  • применяется глобальное состояние.

Пример с callback:

this.$emit('confirm', () => {
  // действие после подтверждения
})

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

При использовании TypeScript опция emits становится особенно важной, так как позволяет строго типизировать события и их аргументы.

emits: {
  submit: (payload: { title: string }) => true
}

Это улучшает автодополнение и снижает количество ошибок при разработке.


Архитектурные принципы использования событий

  • события не изменяют состояние напрямую;
  • компонент не знает, кто обрабатывает событие;
  • события описывают произошедшее, а не желаемый результат;
  • сложная бизнес-логика не должна находиться в дочернем компоненте.

Чёткое разделение ответственности делает систему компонентов предсказуемой и масштабируемой.