Сегодня, в восьмом уроке курса по Vue, состоится ваше первое знакомство с компонентами. Компоненты — это блоки кода, подходящие для многократного использования, которые могут включать в себя и описание внешнего вида частей приложения, и реализацию возможностей проекта. Они помогают программистам в создании модульной кодовой базы, которую удобно поддерживать.
![](https://habrastorage.org/r/w1560/webt/nw/tw/q8/nwtwq8_5dydhsmkhbo4qb_uwfxy.png)
→ 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: компоненты
Основная цель данного урока — создание нашего первого компонента и исследование механизмов передачи данных в компоненты.
Вот код файла
Вот код
Нам не нужно, чтобы во Vue-приложении все данные, методы, вычисляемые свойства размещались бы в корневом экземпляре Vue. Со временем это приведёт к появлению кода, который будет очень тяжело поддерживать. Вместо этого нам хотелось бы разбить код на модульные части, с которыми будет проще работать, и которые сделают разработку более гибкой.
Начнём с того, что возьмём существующий код и перенесём его в новый компонент.
Вот как в файле
Первый аргумент — это выбранное нами имя компонента. Второй — это объект с опциями, похожий на тот, который мы использовали при создании экземпляра Vue на прошлых занятиях.
В экземпляре Vue мы использовали свойство
Опишем шаблон компонента в объекте с опциями:
Во Vue есть несколько способов создания шаблонов. Сейчас мы пользуемся шаблонным литералом, содержимое которого заключено в обратные кавычки.
Если окажется так, что код шаблона не будет размещаться в единственном корневом элементе, в таком, как элемент
Другими словами, шаблон компонента может возвращать только один элемент.
Например, следующий шаблон построен правильно, так как он представлен лишь одним элементом:
А вот если в шаблоне содержится несколько одноуровневых элементов, воспользоваться им не получится. Вот пример неправильного шаблона:
В результате оказывается, что если шаблон должен включать в себя множество элементов, например — набор элементов, заключённых в наш
Теперь, когда в шаблоне находится HTML-код, который раньше был в файле
Как видите, структура этого компонента практически полностью совпадает со структурой экземпляра Vue, с которым мы работали раньше. А вы обратили внимание на то, что
Дело в том, что компоненты часто создают, планируя использовать их многократно. Если у нас будет много компонентов
Теперь, когда мы переместили код, связанный с товаром, в собственный компонент
Сейчас нам осталось лишь разместить компонент
Если теперь перезагрузить страницу приложения — она примет прежний вид.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/e21/c49/c87/e21c49c876cc45eef56194efb747d52c.png)
Страница приложения
Если теперь заглянуть в инструменты разработчика Vue, там можно заметить наличие сущности Root и компонента Product.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/13a/cb2/3dd/13acb23dd2297f8810f87d72226fc8fc.png)
Анализ приложения с помощью инструментов разработчика Vue
А теперь, просто чтобы продемонстрировать возможности многократного использования компонентов, давайте добавим в код
А на странице будет выведено три копии карточки товара.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/853/d0e/57d/853d0e57daa7f5a09acb874754c815c5.png)
Несколько карточек товара, выведенные на одной странице
Обратите внимание на то, что в дальнейшем мы будем работать с одним компонентом
В приложениях часто нужно, чтобы компоненты принимали бы данные, входные параметры, от родительских сущностей. В данном случае родителем компонента
Пусть в корневом экземпляре Vue имеется описание неких данных. Эти данные указывают на то, является ли пользователь обладателем премиум-аккаунта. Код описания экземпляра Vue при этом может выглядеть так:
Давайте решим, что премиум-пользователям полагается бесплатная доставка.
Это означает, что нам нужно, чтобы компонент
Как отправить данные, хранящиеся в свойстве
Во Vue, для передачи данных от родительских сущностей дочерним, применяется свойство объекта с опциями
Начнём работу с описания того, какие именно входные параметры ожидает получить компонент
Обратите внимание на то, что тут используются встроенные возможности Vue по проверке параметров, передаваемых компоненту. А именно, мы указываем то, что типом входного параметра
Далее, внесём в шаблон изменение, выводящее переданные объекту параметры. Выведя значение свойства
Пока всё идёт нормально. Компонент
Но мы пока ещё не передали параметр
Доработаем код в
Обновим страницу.
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/63c/5a0/d97/63c5a0d971d3a8b4108100e1c49acdc8.png)
Вывод данных, переданных компоненту
Теперь входные параметры передаются компоненту. Поговорим о том, что именно мы только что сделали.
Мы передаём компоненту входной параметр, или «пользовательский атрибут», называемый
Теперь корневой экземпляр Vue может передать
Вышеприведённый рисунок, а именно, надпись
Теперь мы убедились в том, что изучаемый нами механизм передачи данных работает так, как ожидается. Если заглянуть в инструменты разработчика Vue, то окажется, что у компонента
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/763/df9/ecb/763df9ecb91be2e555373ed72de15465.png)
Входной параметр компонента
Сейчас, когда данные о том, обладает ли пользователь премиум-аккаунтом, попадают в компонент, давайте используем эти данные для того чтобы вывести на странице сведения о стоимости доставки. Не будем забывать о том, что если параметр
Если в параметре
Уберём из шаблона компонента код вывода значения параметра
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/0e8/028/6fc/0e80286fc67b7956d48221fa875d2ed9.png)
Премиум-пользователь получает бесплатную доставку
Текст
Замечательно! Теперь мы научились передавать данные от родительских сущностей дочерним и смогли воспользоваться этими данными в компоненте для управления стоимостью доставки товаров.
Кстати, стоит отметить, что в дочерних компонентах не следует изменять их входные параметры.
Создайте новый компонент
Вот заготовка, которую вы можете использовать для решения этой задачи.
Вот решение задачи.
Сегодня состоялось ваше первое знакомство с компонентами Vue. Вот что вы узнали:
Пользуетесь ли вы инструментами разработчика 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: компоненты
![](https://habrastorage.org/r/w1560/webt/de/0y/l-/de0yl-6ppopvisr_a80b4yuhjj8.png)
![](https://habrastorage.org/webt/nw/tw/q8/nwtwq8_5dydhsmkhbo4qb_uwfxy.png)
→ 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: компоненты
Цель урока
Основная цель данного урока — создание нашего первого компонента и исследование механизмов передачи данных в компоненты.
Начальный вариант кода
Вот код файла
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>
Если теперь перезагрузить страницу приложения — она примет прежний вид.
![](https://habrastorage.org/getpro/habr/post_images/e21/c49/c87/e21c49c876cc45eef56194efb747d52c.png)
Страница приложения
Если теперь заглянуть в инструменты разработчика Vue, там можно заметить наличие сущности Root и компонента Product.
![](https://habrastorage.org/getpro/habr/post_images/13a/cb2/3dd/13acb23dd2297f8810f87d72226fc8fc.png)
Анализ приложения с помощью инструментов разработчика Vue
А теперь, просто чтобы продемонстрировать возможности многократного использования компонентов, давайте добавим в код
index.html
ещё пару компонентов product
. Собственно говоря, именно так организовано многократное использование компонентов. Код index.html
будет выглядеть так:<div id="app">
<product></product>
<product></product>
<product></product>
</div>
А на странице будет выведено три копии карточки товара.
![](https://habrastorage.org/getpro/habr/post_images/853/d0e/57d/853d0e57daa7f5a09acb874754c815c5.png)
Несколько карточек товара, выведенные на одной странице
Обратите внимание на то, что в дальнейшем мы будем работать с одним компонентом
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>
Обновим страницу.
![](https://habrastorage.org/getpro/habr/post_images/63c/5a0/d97/63c5a0d971d3a8b4108100e1c49acdc8.png)
Вывод данных, переданных компоненту
Теперь входные параметры передаются компоненту. Поговорим о том, что именно мы только что сделали.
Мы передаём компоненту входной параметр, или «пользовательский атрибут», называемый
premium
. Мы привязываем этот пользовательский атрибут, используя конструкцию, представленную двоеточием, к свойству premium
, которое хранится в данных нашего экземпляра Vue.Теперь корневой экземпляр Vue может передать
premium
дочернему компоненту product
. Так как атрибут привязан к свойству premium
из данных экземпляра Vue, текущее значение premium
будет всегда передаваться компоненту product
.Вышеприведённый рисунок, а именно, надпись
User is premium: true
, доказывает то, что всё сделано правильно.Теперь мы убедились в том, что изучаемый нами механизм передачи данных работает так, как ожидается. Если заглянуть в инструменты разработчика Vue, то окажется, что у компонента
Product
теперь есть входной параметр premium
, хранящий значение true
.![](https://habrastorage.org/getpro/habr/post_images/763/df9/ecb/763df9ecb91be2e555373ed72de15465.png)
Входной параметр компонента
Сейчас, когда данные о том, обладает ли пользователь премиум-аккаунтом, попадают в компонент, давайте используем эти данные для того чтобы вывести на странице сведения о стоимости доставки. Не будем забывать о том, что если параметр
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>
, который присутствовал в коде, с которого мы сегодня начали работу, сможет вывести сведения о стоимости доставки.![](https://habrastorage.org/getpro/habr/post_images/0e8/028/6fc/0e80286fc67b7956d48221fa875d2ed9.png)
Премиум-пользователь получает бесплатную доставку
Текст
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: вычисляемые свойства
→ Vue.js для начинающих, урок 8: компоненты
![](https://habrastorage.org/webt/de/0y/l-/de0yl-6ppopvisr_a80b4yuhjj8.png)