Сегодня, в восьмом уроке курса по Vue, состоится ваше первое знакомство с компонентами. Компоненты — это блоки кода, подходящие для многократного использования, которые могут включать в себя и описание внешнего вида частей приложения, и реализацию возможностей проекта. Они помогают программистам в создании модульной кодовой базы, которую удобно поддерживать.
→ 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>
<div
class="color-box"
v-for="(variant, index) in variants"
:key="variant.variantId"
:style="{ backgroundColor: variant.variantColor }"
@mouseover="updateProduct(index)"
></div>
<button
v-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: вычисляемые свойства
Автор: ru_vds