Vue.js для начинающих, урок 8: компоненты

от автора

Сегодня, в восьмом уроке курса по 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: вычисляемые свойства

ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/512658/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *