Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг
Vue.js для начинающих, урок 4: рендеринг списков
Vue.js для начинающих, урок 5: обработка событий
Vue.js для начинающих, урок 6: привязка классов и стилей
Vue.js для начинающих, урок 7: вычисляемые свойства
Цель урока
Основная цель данного урока создание нашего первого компонента и исследование механизмов передачи данных в компоненты.
Начальный вариант кода
Вот код файла
index.html
, находящийся в теге
<body>
, с которого мы начнём работу:
<div id="app"><div class="product"><div class="product-image"><img :src="image" /></div><div class="product-info"><h1>{{ title }}</h1><p v-if="inStock">In stock</p><p v-else>Out of Stock</p><p>Shipping: {{ shipping }}</p><ul><li v-for="detail in details">{{ detail }}</li></ul><divclass="color-box"v-for="(variant, index) in variants":key="variant.variantId":style="{ backgroundColor: variant.variantColor }"@mouseover="updateProduct(index)"></div><buttonv-on:click="addToCart":disabled="!inStock":class="{ disabledButton: !inStock }">Add to cart</button><div class="cart"><p>Cart({{ cart }})</p></div></div></div></div>
Вот код
main.js
:
var app = new Vue({el: '#app',data: {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;}}})
Задача
Нам не нужно, чтобы во Vue-приложении все данные, методы, вычисляемые свойства размещались бы в корневом экземпляре Vue. Со временем это приведёт к появлению кода, который будет очень тяжело поддерживать. Вместо этого нам хотелось бы разбить код на модульные части, с которыми будет проще работать, и которые сделают разработку более гибкой.
Решение задачи
Начнём с того, что возьмём существующий код и перенесём его в новый компонент.
Вот как в файле
main.js
регистрируется компонент:
Vue.component('product', {})
Первый аргумент это выбранное нами имя компонента. Второй это объект с опциями, похожий на тот, который мы использовали при создании экземпляра Vue на прошлых занятиях.
В экземпляре Vue мы использовали свойство
el
для
организации его привязки к элементу DOM. В случае с компонентом
используется свойство template
, которое определяет
HTML-код компонента.Опишем шаблон компонента в объекте с опциями:
Vue.component('product', {template: `<div class="product"> // Здесь будет весь HTML-код, который раньше был в элементе с классом product</div>`})
Во Vue есть несколько способов создания шаблонов. Сейчас мы пользуемся шаблонным литералом, содержимое которого заключено в обратные кавычки.
Если окажется так, что код шаблона не будет размещаться в единственном корневом элементе, в таком, как элемент
<div>
с классом product
, это
приведёт к выводу такого сообщения об ошибке:
Component template should contain exactly one root element
Другими словами, шаблон компонента может возвращать только один элемент.
Например, следующий шаблон построен правильно, так как он представлен лишь одним элементом:
Vue.component('product', {template: `<h1>I'm a single element!</h1>`})
А вот если в шаблоне содержится несколько одноуровневых элементов, воспользоваться им не получится. Вот пример неправильного шаблона:
Vue.component('product', {template: `<h1>I'm a single element!</h1><h2>Not anymore</h2>`})
В результате оказывается, что если шаблон должен включать в себя множество элементов, например набор элементов, заключённых в наш
<div>
с классом product
, эти
элементы должны быть помещены во внешний элемент-контейнер. В
результате в шаблоне будет лишь один корневой элемент.Теперь, когда в шаблоне находится HTML-код, который раньше был в файле
index.html
, мы можем добавить в компонент
данные, методы, вычисляемые свойства, которые раньше были в
корневом экземпляре Vue:
Vue.component('product', {template: `<div class="product"></div>`,data() {return {// тут будут данные}},methods: {// тут будут методы},computed: {// тут будут вычисляемые свойства}})
Как видите, структура этого компонента практически полностью совпадает со структурой экземпляра Vue, с которым мы работали раньше. А вы обратили внимание на то, что
data
это
теперь не свойство, а метод объекта с опциями? Почему это так?Дело в том, что компоненты часто создают, планируя использовать их многократно. Если у нас будет много компонентов
product
, нам нужно обеспечить то, чтобы для каждого из
них создавались бы собственные экземпляры сущности
data
. Так как data
это теперь функция,
которая возвращает объект с данными, каждый компонент
гарантированно получит собственный набор данных. Если бы сущность
data
не была бы функцией, то каждый компонент
product
, везде, где использовались бы такие
компоненты, содержал бы одни и те же данные. А это противоречит
идее многократного использования компонентов.Теперь, когда мы переместили код, связанный с товаром, в собственный компонент
product
, код описания корневого
экземпляра Vue будет выглядеть так:
var app = new Vue({el: '#app'})
Сейчас нам осталось лишь разместить компонент
product
в коде файла index.html
. Это будет выглядеть так:
<div id="app"><product></product></div>
Если теперь перезагрузить страницу приложения она примет прежний вид.
Страница приложения
Если теперь заглянуть в инструменты разработчика Vue, там можно заметить наличие сущности Root и компонента Product.
Анализ приложения с помощью инструментов разработчика Vue
А теперь, просто чтобы продемонстрировать возможности многократного использования компонентов, давайте добавим в код
index.html
ещё пару компонентов product
.
Собственно говоря, именно так организовано многократное
использование компонентов. Код index.html
будет
выглядеть так:
<div id="app"><product></product><product></product><product></product></div>
А на странице будет выведено три копии карточки товара.
Несколько карточек товара, выведенные на одной странице
Обратите внимание на то, что в дальнейшем мы будем работать с одним компонентом
product
, поэтому код
index.html
будет выглядеть так:
<div id="app"><product></product></div>
Задача
В приложениях часто нужно, чтобы компоненты принимали бы данные, входные параметры, от родительских сущностей. В данном случае родителем компонента
product
является сам корневой
экземпляр Vue.Пусть в корневом экземпляре Vue имеется описание неких данных. Эти данные указывают на то, является ли пользователь обладателем премиум-аккаунта. Код описания экземпляра Vue при этом может выглядеть так:
var app = new Vue({el: '#app',data: {premium: true}})
Давайте решим, что премиум-пользователям полагается бесплатная доставка.
Это означает, что нам нужно, чтобы компонент
product
выводил бы, в зависимости от того, что записано в свойство
premium
корневого экземпляра Vue, разные сведения о
стоимости доставки.Как отправить данные, хранящиеся в свойстве
premium
корневого экземпляра Vue, дочернему элементу, которым является
компонент product
?Решение задачи
Во Vue, для передачи данных от родительских сущностей дочерним, применяется свойство объекта с опциями
props
,
описываемое у компонентов. Это объект с описанием входных
параметров компонента, значения которых должны быть заданы на
основе данных, получаемых от родительской сущности.Начнём работу с описания того, какие именно входные параметры ожидает получить компонент
product
. Для этого добавим
в объект с опциями, используемый при его создании, соответствующее
свойство:
Vue.component('product', {props: {premium: {type: Boolean,required: true}},// Тут будут описания данных, методов, вычисляемых свойств})
Обратите внимание на то, что тут используются встроенные возможности Vue по проверке параметров, передаваемых компоненту. А именно, мы указываем то, что типом входного параметра
premium
является Boolean
, и то, что этот
параметр является обязательным, устанавливая required
в true
.Далее, внесём в шаблон изменение, выводящее переданные объекту параметры. Выведя значение свойства
premium
на
странице, мы убедимся в правильности работы исследуемого нами
механизма.
<p>User is premium: {{ premium }}</p>
Пока всё идёт нормально. Компонент
product
знает о
том, что он будет получать необходимый для его работы параметр типа
Boolean
. Мы подготовили место для вывода
соответствующих данных.Но мы пока ещё не передали параметр
premium
компоненту. Сделать это можно с помощью пользовательского атрибута,
который похож на трубопровод, ведущий к компоненту, через который
ему можно передавать входные параметры, и, в частности,
premium
.Доработаем код в
index.html
:
<div id="app"><product :premium="premium"></product></div>
Обновим страницу.
Вывод данных, переданных компоненту
Теперь входные параметры передаются компоненту. Поговорим о том, что именно мы только что сделали.
Мы передаём компоненту входной параметр, или пользовательский атрибут, называемый
premium
. Мы привязываем этот
пользовательский атрибут, используя конструкцию, представленную
двоеточием, к свойству premium
, которое хранится в
данных нашего экземпляра Vue.Теперь корневой экземпляр Vue может передать
premium
дочернему компоненту product
. Так как атрибут привязан
к свойству premium
из данных экземпляра Vue, текущее
значение premium
будет всегда передаваться компоненту
product
.Вышеприведённый рисунок, а именно, надпись
User is premium:
true
, доказывает то, что всё сделано правильно.Теперь мы убедились в том, что изучаемый нами механизм передачи данных работает так, как ожидается. Если заглянуть в инструменты разработчика Vue, то окажется, что у компонента
Product
теперь есть входной параметр
premium
, хранящий значение true
.Входной параметр компонента
Сейчас, когда данные о том, обладает ли пользователь премиум-аккаунтом, попадают в компонент, давайте используем эти данные для того чтобы вывести на странице сведения о стоимости доставки. Не будем забывать о том, что если параметр
premium
установлен в значение true
, то
пользователю полагается бесплатная доставка. Создадим новое
вычисляемое свойство shipping
и воспользуемся в нём
параметром premium
:
shipping() {if (this.premium) {return "Free";} else {return 2.99}}
Если в параметре
this.premium
хранится
true
вычисляемое свойство shipping
вернёт
Free
. В противном случае оно вернёт
2.99
.Уберём из шаблона компонента код вывода значения параметра
premium
. Теперь элемент <p>Shipping: {{
shipping }}</p>
, который присутствовал в коде, с
которого мы сегодня начали работу, сможет вывести сведения о
стоимости доставки.Премиум-пользователь получает бесплатную доставку
Текст
Shipping: Free
появляется на странице из-за
того, что компоненту передан входной параметр premium
,
установленный в значение true
.Замечательно! Теперь мы научились передавать данные от родительских сущностей дочерним и смогли воспользоваться этими данными в компоненте для управления стоимостью доставки товаров.
Кстати, стоит отметить, что в дочерних компонентах не следует изменять их входные параметры.
Практикум
Создайте новый компонент
product-details
, который
должен использовать входной параметр details
и
отвечать за визуализацию той части карточки товара, которая раньше
формировалась с использованием следующего кода:
<ul><li v-for="detail in details">{{ detail }}</li></ul>
Вот заготовка, которую вы можете использовать для решения этой задачи.
Вот решение задачи.
Итоги
Сегодня состоялось ваше первое знакомство с компонентами Vue. Вот что вы узнали:
- Компоненты это блоки кода, представленные в виде пользовательских элементов.
- Компоненты упрощают управление приложением благодаря тому, что позволяют разделить его на части, подходящие для многократного использования. Они содержат в себе описания визуальной составляющей и функционала соответствующей части приложения.
- Данные компонента представлены методом
data()
объекта с опциями. - Для передачи данных от родительских сущностей дочерним
сущностям используются входные параметры (
props
). - Мы можем описать требования к входным параметрам, которые принимает компонент.
- Входные параметры передаются компонентам через пользовательские атрибуты.
- Данные родительского компонента можно динамически привязать к пользовательским атрибутам.
- Инструменты разработчика Vue дают ценные сведения о компонентах.
Пользуетесь ли вы инструментами разработчика Vue?
Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг
Vue.js для начинающих, урок 4: рендеринг списков
Vue.js для начинающих, урок 5: обработка событий
Vue.js для начинающих, урок 6: привязка классов и стилей
Vue.js для начинающих, урок 7: вычисляемые свойства