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

Сегодня, в восьмом уроке курса по Vue, состоится ваше первое знакомство с компонентами. Компоненты — это блоки кода, подходящие для многократного использования, которые могут включать в себя и описание внешнего вида частей приложения, и реализацию возможностей проекта. Они помогают программистам в создании модульной кодовой базы, которую удобно поддерживать.

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: вычисляемые свойства

Цель урока


Основная цель данного урока — создание нашего первого компонента и исследование механизмов передачи данных в компоненты.

Начальный вариант кода


Вот код файла index.html, находящийся в теге , с которого мы начнём работу:

  
    
           
    
      

{{ title }}

      

In stock

      

Out of Stock

      

Shipping: {{ shipping }}

      
            
  • {{ detail }}
  •       
      
             
        

Cart({{ cart }})

      
    
  


Вот код 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: `
    
… // Здесь будет весь HTML-код, который раньше был в элементе с классом product     
  ` })


Во Vue есть несколько способов создания шаблонов. Сейчас мы пользуемся шаблонным литералом, содержимое которого заключено в обратные кавычки.

Если окажется так, что код шаблона не будет размещаться в единственном корневом элементе, в таком, как элемент

с классом product, это приведёт к выводу такого сообщения об ошибке:

Component template should contain exactly one root element


Другими словами, шаблон компонента может возвращать только один элемент.

Например, следующий шаблон построен правильно, так как он представлен лишь одним элементом:

Vue.component('product', {
  template: `

I'm a single element!

` })


А вот если в шаблоне содержится несколько одноуровневых элементов, воспользоваться им не получится. Вот пример неправильного шаблона:

Vue.component('product', {
  template: `
    

I'm a single element!

    

Not anymore

    ` })


В результате оказывается, что если шаблон должен включать в себя множество элементов, например — набор элементов, заключённых в наш

с классом product, эти элементы должны быть помещены во внешний элемент-контейнер. В результате в шаблоне будет лишь один корневой элемент.

Теперь, когда в шаблоне находится HTML-код, который раньше был в файле index.html, мы можем добавить в компонент данные, методы, вычисляемые свойства, которые раньше были в корневом экземпляре Vue:

Vue.component('product', {
  template: `
  
…   
  `,   data() {     return {       // тут будут данные     }   },     methods: {       // тут будут методы     },     computed: {       // тут будут вычисляемые свойства     } })


Как видите, структура этого компонента практически полностью совпадает со структурой экземпляра Vue, с которым мы работали раньше. А вы обратили внимание на то, что data — это теперь не свойство, а метод объекта с опциями? Почему это так?

Дело в том, что компоненты часто создают, планируя использовать их многократно. Если у нас будет много компонентов product, нам нужно обеспечить то, чтобы для каждого из них создавались бы собственные экземпляры сущности data. Так как data — это теперь функция, которая возвращает объект с данными, каждый компонент гарантированно получит собственный набор данных. Если бы сущность data не была бы функцией, то каждый компонент product, везде, где использовались бы такие компоненты, содержал бы одни и те же данные. А это противоречит идее многократного использования компонентов.

Теперь, когда мы переместили код, связанный с товаром, в собственный компонент product, код описания корневого экземпляра Vue будет выглядеть так:

var app = new Vue({
  el: '#app'
})


Сейчас нам осталось лишь разместить компонент product в коде файла index.html. Это будет выглядеть так:

  


Если теперь перезагрузить страницу приложения — она примет прежний вид.

e21c49c876cc45eef56194efb747d52c.png


Страница приложения

Если теперь заглянуть в инструменты разработчика Vue, там можно заметить наличие сущности Root и компонента Product.

13acb23dd2297f8810f87d72226fc8fc.png


Анализ приложения с помощью инструментов разработчика Vue

А теперь, просто чтобы продемонстрировать возможности многократного использования компонентов, давайте добавим в код index.html ещё пару компонентов product. Собственно говоря, именно так организовано многократное использование компонентов. Код index.html будет выглядеть так:

        


А на странице будет выведено три копии карточки товара.

853d0e57daa7f5a09acb874754c815c5.png


Несколько карточек товара, выведенные на одной странице

Обратите внимание на то, что в дальнейшем мы будем работать с одним компонентом product, поэтому код index.html будет выглядеть так:

  


Задача


В приложениях часто нужно, чтобы компоненты принимали бы данные, входные параметры, от родительских сущностей. В данном случае родителем компонента 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 на странице, мы убедимся в правильности работы исследуемого нами механизма.

User is premium: {{ premium }}


Пока всё идёт нормально. Компонент product знает о том, что он будет получать необходимый для его работы параметр типа Boolean. Мы подготовили место для вывода соответствующих данных.

Но мы пока ещё не передали параметр premium компоненту. Сделать это можно с помощью пользовательского атрибута, который похож на «трубопровод», ведущий к компоненту, через который ему можно передавать входные параметры, и, в частности, premium.

Доработаем код в index.html:

  


Обновим страницу.

63c5a0d971d3a8b4108100e1c49acdc8.png


Вывод данных, переданных компоненту

Теперь входные параметры передаются компоненту. Поговорим о том, что именно мы только что сделали.

Мы передаём компоненту входной параметр, или «пользовательский атрибут», называемый premium. Мы привязываем этот пользовательский атрибут, используя конструкцию, представленную двоеточием, к свойству premium, которое хранится в данных нашего экземпляра Vue.

Теперь корневой экземпляр Vue может передать premium дочернему компоненту product. Так как атрибут привязан к свойству premium из данных экземпляра Vue, текущее значение premium будет всегда передаваться компоненту product.

Вышеприведённый рисунок, а именно, надпись User is premium: true, доказывает то, что всё сделано правильно.

Теперь мы убедились в том, что изучаемый нами механизм передачи данных работает так, как ожидается. Если заглянуть в инструменты разработчика Vue, то окажется, что у компонента Product теперь есть входной параметр premium, хранящий значение true.

763df9ecb91be2e555373ed72de15465.png


Входной параметр компонента

Сейчас, когда данные о том, обладает ли пользователь премиум-аккаунтом, попадают в компонент, давайте используем эти данные для того чтобы вывести на странице сведения о стоимости доставки. Не будем забывать о том, что если параметр premium установлен в значение true, то пользователю полагается бесплатная доставка. Создадим новое вычисляемое свойство shipping и воспользуемся в нём параметром premium:

shipping() {
  if (this.premium) {
    return "Free";
  } else {
    return 2.99
  }
}


Если в параметре this.premium хранится true — вычисляемое свойство shipping вернёт Free. В противном случае оно вернёт 2.99.

Уберём из шаблона компонента код вывода значения параметра premium. Теперь элемент

Shipping: {{ shipping }}

, который присутствовал в коде, с которого мы сегодня начали работу, сможет вывести сведения о стоимости доставки.

0e80286fc67b7956d48221fa875d2ed9.png


Премиум-пользователь получает бесплатную доставку

Текст Shipping: Free появляется на странице из-за того, что компоненту передан входной параметр premium, установленный в значение true.

Замечательно! Теперь мы научились передавать данные от родительских сущностей дочерним и смогли воспользоваться этими данными в компоненте для управления стоимостью доставки товаров.

Кстати, стоит отметить, что в дочерних компонентах не следует изменять их входные параметры.

Практикум


Создайте новый компонент product-details, который должен использовать входной параметр details и отвечать за визуализацию той части карточки товара, которая раньше формировалась с использованием следующего кода:

      
  • {{ detail }}


Вот заготовка, которую вы можете использовать для решения этой задачи.

Вот решение задачи.

Итоги


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

de0yl-6ppopvisr_a80b4yuhjj8.png

© Habrahabr.ru