[Перевод] Vue.js для начинающих, урок 9: пользовательские события
На предыдущем уроке нашего курса по Vue вы узнали о том, как создавать компоненты, и о том, как передавать данные от родительских сущностей дочерним с использованием механизма входных параметров (props). А что если данные нужно передавать в обратном направлении? Сегодня, в девятом уроке, вы узнаете о том, как наладить двустороннюю связь между компонентами разного уровня.
→ Vue.js для начинающих, урок 1: экземпляр Vue
→ Vue.js для начинающих, урок 2: привязка атрибутов
→ Vue.js для начинающих, урок 3: условный рендеринг
→ Vue.js для начинающих, урок 4: рендеринг списков
→ Vue.js для начинающих, урок 5: обработка событий
→ Vue.js для начинающих, урок 6: привязка классов и стилей
→ Vue.js для начинающих, урок 7: вычисляемые свойства
→ Vue.js для начинающих, урок 8: компоненты
Цель урока
Нам надо, чтобы компонент product
мог бы сообщать родительской сущности, корневому экземпляру Vue, о том, что произошло некое событие. При этом product
должен отправлять, вместе с уведомлением о возникновении события, некие данные.
Начальный вариант кода
В файле index.html
учебного проекта сейчас находится такой код:
Вот — содержимое файла main.js
:
Vue.component('product', {
props: {
premium: {
type: Boolean,
required: true
}
},
template: `
{{ title }}
In stock
Out of Stock
Shipping: {{ shipping }}
- {{ detail }}
Cart({{ cart }})
`,
data() {
return {
product: 'Socks',
brand: 'Vue Mastery',
selectedVariant: 0,
details: ['80% cotton', '20% polyester', 'Gender-neutral'],
variants: [
{
variantId: 2234,
variantColor: 'green',
variantImage: './assets/vmSocks-green.jpg',
variantQuantity: 10
},
{
variantId: 2235,
variantColor: 'blue',
variantImage: './assets/vmSocks-blue.jpg',
variantQuantity: 0
}
],
cart: 0,
}
},
methods: {
addToCart() {
this.cart += 1;
},
updateProduct(index) {
this.selectedVariant = index;
console.log(index);
}
},
computed: {
title() {
return this.brand + ' ' + this.product;
},
image() {
return this.variants[this.selectedVariant].variantImage;
},
inStock() {
return this.variants[this.selectedVariant].variantQuantity;
},
shipping() {
if (this.premium) {
return "Free";
} else {
return 2.99
}
}
}
})
var app = new Vue({
el: '#app',
data: {
premium: true
}
})
Задача
Теперь, когда product
представлен самостоятельным компонентом, то, что код, имеющий отношение к корзине, находится в product
, смысла не имеет. Если у каждого товара будет своя корзина, за состоянием которой нам нужно наблюдать, в нашем приложении заведётся большой беспорядок. Вместо этого мы хотели бы, чтобы корзина существовала бы на уровне корневого экземпляра Vue. Так же нам нужно, чтобы компонент product
сообщал бы корневому экземпляру Vue о добавлении товаров в корзину, то есть — о нажатиях на кнопку Add to cart
.
Решение
Переместим данные, имеющие отношение к корзине, обратно в корневой экземпляр Vue:
var app = new Vue({
el: '#app',
data: {
premium: true,
cart: 0
}
})
Далее — перенесём шаблон корзины обратно в index.html
, приведя его код к такому виду:
Cart({{ cart }})
Теперь, если открыть страницу приложения в браузере и нажать на кнопку Add to cart
, ничего, как и ожидается, не произойдёт.
Нажатие на кнопку Add to cart пока ни к чему не приводит
А что должно происходить по нажатию на эту кнопку? Нам нужно, чтобы при нажатии на неё корневой экземпляр Vue получал бы уведомление, которое вызывало бы метод, приводящий корзину в актуальное состояние, то есть, обновляющий значение, которое хранится в cart
.
Для того чтобы этого добиться, давайте сначала перепишем код метода addToCart
компонента product
.
Сейчас он выглядит так:
addToCart() {
this.cart += 1;
},
Приведём его к такому виду:
addToCart() {
this.$emit('add-to-cart');
},
Что всё это значит?
А значит это вот что. Когда вызывается метод addToCart
, генерируется пользовательское событие с именем add-to-cart
. Другими словами — когда нажимают на кнопку Add to cart
, вызывается метод, генерирующий событие, сообщающее о том, что только что была нажата кнопка (то есть — что только что произошло событие, вызванное нажатием кнопки).
Но прямо сейчас ничто в приложении не ожидает появления этого события, не прослушивает его. Давайте добавим прослушиватель событий в index.html
:
Код Этот метод, который теперь будет объявлен в объекте с опциями, используемом при создании экземпляра Vue, вы, точно, уже где-то видели: При нажатии на кнопку, находящуюся в компоненте Мы добились своей цели, но в реальном приложении знание о том, что произошло некое событие, что некий товар добавлен в корзину, особой пользы не принесёт. В реальности нужно знать хотя бы о том, какой именно товар добавлен в корзину. А это значит, что в событии, которое генерируется в ответ на нажатие на кнопку, нужно передавать ещё и какие-то данные. Данные, передаваемые в событии, можно описать в виде второго аргумента, передаваемого Нам не нужно выводить на странице весь массив. Нас устроит вывод количества товаров, добавленных в корзину, то есть — в массив Cart({{ cart.length }}) Теперь мы просто выводим на странице длину массива, или, другими словами, количество товаров в корзине. Внешне корзина выглядит так же, как и прежде, но теперь мы, вместо простого увеличения значения числового свойства
Тут мы пользуемся конструкцией вида @add-to-card
аналогично тому, как пользуемся конструкцией :premium
. Но если :premium
— это «трубопровод», по которому можно передавать данные дочернему компоненту от родительского, то @add-to-cart
можно сравнить с «радиоприёмником» родительского компонента, который принимает от дочернего компонента сведения о том, что была нажата кнопка Add to cart
. Так как «радиоприёмник» находится в теге
, вложенном в Add to cart
будет вызван метод updateCart
, находящийся в корневом экземпляре Vue.
@add-to-cart=«updateCart»
в переводе на обычный язык выглядит так: «Когда услышишь, что произошло событие add-to-cart
, вызови метод updateCart
».methods: {
updateCart() {
this.cart += 1;
}
}
Собственно говоря, это — точно такой же метод, который использовался раньше в product
. Но теперь он находится в корневом экземпляре Vue и вызывается по нажатию на кнопку Add to cart
.
Кнопка снова работаетproduct
, вызывается метод addToCart
, который генерирует событие. Корневой экземпляр Vue, «слушая радио», узнаёт о том, что данное событие произошло и вызывает метод updateCart
, который увеличивает число, хранящееся в cart
.$emit
в коде метода addToCart
компонента product
: this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);
Теперь в событии передаётся идентификатор (variantId
) товара, который пользователь хочет добавить в корзину. Это значит, что мы, вместо того чтобы просто увеличивать количество товара в корзине, можем пойти дальше и хранить в корзине более подробные сведения о добавленных в неё товарах. Для этого сначала преобразуем корзину в массив, записав пустой массив в cart
: cart: []
Далее — перепишем метод updateCart
. Во-первых — он теперь будет принимать id
— тот самый идентификатор товара, который передаётся теперь в событии, во-вторых — он теперь будет помещать то, что получил, в массив: methods: {
updateCart(id) {
this.cart.push(id);
}
}
После однократного нажатия на кнопку в массив попадает идентификатор товара. Массив выводится на странице.
Массив с идентификатором товара выводится на страницеcart
. Поэтому мы можем переписать код тега , в котором выводятся сведения о количестве товаров, добавленных в корзину, так:
На странице выводятся сведения о количестве товаров, добавленных в корзинуcart
, сохраняем в массиве cart
сведения о том, какой именно товар был добавлен в корзину.Практикум
Добавьте в проект кнопку, которая удаляет из массива cart
товар, добавленный туда ранее. По нажатию на эту кнопку должно генерироваться событие, содержащее сведения об идентификаторе товара, который нужно убрать из корзины.Итоги
Вот что вы сегодня узнали: $emit
.v-on
(или её сокращённой версии @
), для организации реакции на события, генерируемые дочерними компонентами. Если событие происходит — в родительском компоненте может быть вызван обработчик события.
Если вы изучаете курс и дошли до этого урока — просим рассказать о том, с какой целью вы занимаетесь, чего хотите достичь, освоив Vue.
→ Vue.js для начинающих, урок 1: экземпляр Vue
→ Vue.js для начинающих, урок 2: привязка атрибутов
→ Vue.js для начинающих, урок 3: условный рендеринг
→ Vue.js для начинающих, урок 4: рендеринг списков
→ Vue.js для начинающих, урок 5: обработка событий
→ Vue.js для начинающих, урок 6: привязка классов и стилей
→ Vue.js для начинающих, урок 7: вычисляемые свойства
→ Vue.js для начинающих, урок 8: компоненты