В Vue данные обычно передаются от родительских компонентов к их дочерним однонаправленно. Это передается с помощью props, которые являются свойствами или атрибутами, которые мы даем компонентам.
Например, если мы назовем компонент PageOne
, который имеет свойство name
, то это свойство name
станет доступным в самом компоненте PageOne
, позволяя нам делать с ним то, что мы хотим. Таким образом, данные передаются в дочерний компонент, когда мы объявляем их в родительском компоненте или странице:
В большинстве сценариев реквизиты позволяют нам делать с данными все, что нам нужно. Однако иногда нам нужно передавать данные вверх — от дочернего компонента к его родителю. Для этого мы используем $emit
, который позволяет нам отправлять данные вверх, а затем запускать событие в родительском компоненте, если произойдет событие $emit
.
Как работает $emit в Vue
Есть три способа вызвать $emit
в Vue, в зависимости от того, используете ли вы API Options, API Composition или инлайнинг событий $emit
. Если вы не уверены, вы можете прочитать о разнице между Composition API и Options API здесь.
Давайте рассмотрим, как это работает, на глупом примере. Допустим, у нас есть компонент счетчика, который выглядит следующим образом:
<template>
<button @click="$emit('counterEvent')">Click Me</button>
</template>
Этот компонент хранится в файле под названием Counter.vue
. Наш компонент не может быть изменен, поскольку он используется в других местах, но у него есть событие $emit
, запускаемое при каждом щелчке на нем. Это прекрасно, поскольку мы можем использовать его в нашем родительском компоненте.
А что если мы захотим добавить этот компонент куда-нибудь — например, в наш файл App.vue
— и использовать его для отображения значения нашего счетчика. Давайте попробуем сделать это сейчас:
<template>
<h1>{{ counter }}</h1>
<Counter @counter-event="incrCounter"/>
</template>
<script>
import Counter from './Counter.vue'
export default {
// Add our components
components: {
Counter
},
// Store our data
data() {
return {
counter: 0
}
},
methods: {
incrCounter: function() {
this.counter += 1;
}
}
}
</script>
Давайте разберем это на части — во-первых, мы включаем наш Counter
. Поскольку у него есть событие $emit
под названием counterEvent
, мы можем прикрепить его к нашему Counter
HTML. Каждый раз, когда $emit
срабатывает, срабатывает counterEvent
, а значит и функция в этом свойстве. Здесь мы запускаем incrCounter
каждый раз, когда срабатывает counterEvent
.
Делая это, мы также увеличиваем наши данные counter
на 1, поскольку именно это делает incrCounter
. Таким образом, мы передали событие щелчка вверх нашему родительскому компоненту.
Случай с кебабом
Вы можете заметить, что когда мы определяли наше событие $emit
, мы использовали верблюжий регистр (counterEvent
), но при отслеживании события мы использовали кебабный регистр (counter-event
).
В Vue 3 можно использовать counterEvent
и counter-event
взаимозаменяемо, поскольку Vue 3 автоматически преобразует counterEvent
в counter-event
. В Vue 2 этой функции нет, поэтому просто используйте counter-event
для обоих вариантов.
Передача данных с помощью $emit
Допустим, вместо этого мы хотим, чтобы наш компонент определял, на сколько должен увеличиться counterEvent
. Если мы хотим сделать это, мы можем передать второй аргумент функции $emit
, который является значением:
<template>
<button @click="$emit('counterEvent', 2)">Click Me</button>
</template>
Здесь мы передаем значение 2
нашему counterEvent
. Давайте вернемся к нашему файлу App.vue
. Чтобы использовать это значение в counterEvent
, нам нужно записать его как функцию. Ниже, n
— это значение:
<template>
<h1>{{ counter }}</h1>
<Counter @counter-event="(n) => incrCounter(n)"/>
</template>
<script>
import Counter from './Counter.vue'
export default {
// Add our components
components: {
Counter
},
// Store our data
data() {
return {
counter: 0
}
},
methods: {
incrCounter: function(value) {
this.counter += value;
}
}
}
</script>
Теперь наш счетчик увеличится на значение, помещенное в дочерний компонент, что позволит нам передавать данные и в родительский компонент. Как и следовало ожидать, это не ограничивается только числами, но может включать любую структуру данных — включая объекты и строки.
Использование $emit с API Options
Мы показали довольно простой пример, но мы также могли бы написать наш дочерний компонент Counter.vue
с использованием функции. Вот пример с API Options, использующий this.$emit
:
<template>
<button @click="emitFunction">Click Me</button>
</template>
<script>
export default {
emits: [ 'counterEvent' ],
methods: {
emitFunction: function() {
this.$emit('counterEvent', 2)
}
}
}
</script>
Это может оказаться немного более чистым способом использования $emit
, особенно если вы хотите делать другие вещи наряду с использованием $emit
при нажатии на кнопку.
Добавление событий emit в прототип
Вы можете заметить, что мы также определили наше событие emit в emits
на прототипе. Это хорошая практика по двум причинам:
- Это позволяет вам самодокументировать код, показывая, какие события emit возможны в данном компоненте.
- Это помогает отслеживать устаревшие события, поскольку Vue будет выдавать ошибку, если используется событие emit, но оно не найдено в массиве
emits
.
Использование $emit с API композиции
Мы можем использовать $emit
с API Composition — единственная разница в том, что вместо этого мы должны использовать defineEmits
.
<template>
<button @click="emitFunction">Click Me</button>
</template>
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['counterEvent']);
const emitFunction = function() {
emit('counterEvent', 2)
}
</script>
defineEmits
используется для определения полного списка всех допустимых событий emit
. Здесь у нас только одно, counterEvent
. Если бы у вас было несколько, вы могли бы определить их так:
const emit = defineEmits(['counterEvent', 'anotherEvent', 'finalEvent']);
Если вы используете событие emit, не указанное в defineEmits
, Vue выдаст предупреждение, аналогичное использованию emits
в API Options. В противном случае вы можете использовать функцию emit()
, чтобы эмитировать как обычно, без необходимости использовать API Options.
Заключительные мысли и лучшие практики
Emit — это мощный инструмент для отправки данных обратно в родительский модуль, когда это необходимо. Это означает, что потоки данных в Vue могут быть двусторонними. При определении кода emit
следует придерживаться двух основных лучших практик:
- Всегда определяйте события emit либо в
emits
, либо вdefineEmits
, что поможет вам сохранить ваш код чистым и хорошо документированным. - Обычным соглашением в Vue 3 является использование кебабного регистра (
this-is-kebab-case
) в HTML, и верблюжьего регистра (thisIsCamelCase
) в сценарии. Поэтому и здесь лучше всего следовать этому соглашению.
Надеюсь, вам понравилось это руководство о том, как работает $emit
. Оставайтесь с нами, чтобы узнать больше о Vue.