Русский
Русский
English
Статистика
Реклама

Vue.js

Перевод Vue.js для начинающих, урок 2 привязка атрибутов

08.07.2020 16:06:56 | Автор: admin
Вот перевод второго урока учебного курса по Vue.js. Здесь речь пойдёт о привязке атрибутов, о подключении данных, хранящихся в экземпляре Vue, к атрибутам HTML-элементов.



Первый урок

Цель урока


Здесь мы разберёмся с тем, как, используя привязку атрибутов, вывести изображение, и задать текст атрибута alt. Соответствующие данные мы возьмём из экземпляра Vue.

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


Начнём работу с такого HTML-кода, находящегося в файле index.html, в теге <body>:

<div id="app"><div class="product"><div class="product-image"><img src="" /></div><div class="product-info"><h1>{{ product }}</h1></div></div></div>

Обратите внимание на тег <div> с классом product-image. Именно в нём содержится элемент <img>, к которому мы хотим динамически привязать данные, необходимые для вывода изображения.

Элемент <div> с классом product-info используется для вывода названия товара.

Вот JavaScript-код, содержащийся в файле main.js:

var app = new Vue({el: '#app',data: {product: "Socks",image: "./assets/vmSocks-green.jpg"}})

Обратите внимание на то, что в объекте data теперь имеется новое свойство image, содержащее путь к изображению.

Здесь можно найти CSS-код, используемый в этом уроке.

Для подключения стиля к index.html нужно добавить в тег <head> следующее:

<link rel="stylesheet" type="text/css" href="style.css"

Тут мы исходит из предположения о том, что файл со стилями имеет имя style.css и хранится в той же папке, что и index.html.

Здесь находится изображение, которое мы будем выводить на странице.

Задача


Нам нужно, чтобы на странице вывелось бы изображение. При этом мы хотим динамически управлять этим изображением. То есть, нам нужна возможность, позволяющая менять путь к изображению, хранящийся в экземпляре Vue, и тут же видеть результаты этих изменений на странице. Так как именно атрибут src элемента <img> отвечает за то, какое изображение выведет элемент, нам нужно привязать некие данные к этому атрибуту. Это и позволит нам динамически, основываясь на данных, хранящихся в экземпляре Vue, менять изображение.

Важный термин: привязка данных


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

Другими словами, сущность-источник данных, связана с сущностью, в которой эти данные используются, с приёмником данных. В нашем случае источник данных это экземпляр Vue, а приёмник это атрибут src элемента <img>.

Решение задачи


Для того чтобы привязать значение свойства image из объекта с данными к свойству src тега <img>, мы воспользуемся директивой Vue v-bind. Перепишем код тега <img> из файла index.html:

<img v-bind:src="image" />

Когда Vue, обрабатывая страницу, видит такую конструкцию, фреймворк заменяет её на следующий HTML-код:

<img src="./assets/vmSocks-green.jpg" />

Если всё сделано правильно, то на странице будет выведено изображение.


Изображение зелёных носков выведено на странице

А если поменять значение свойства image объекта data, то соответствующим образом изменится и значение атрибута src, что приведёт к выводу на странице нового изображения.

Предположим, нам надо заменить изображение зелёных носков на изображение синих. Для этого, учитывая то, что путь к файлу с новым изображением выглядит как ./assets/vmSocks-blue.jpg (файл изображения можно найти здесь), достаточно привести код описания свойства image в объекте data к такому виду:

image: "./assets/vmSocks-blue.jpg"

Это приведёт к тому, что на странице появится изображение синих носков.


Изображение синих носков выведено на странице

Дополнительные варианты использования v-bind


Директиву v-bind можно использовать не только с атрибутом src. Она может помочь нам и в динамической настройке атрибута изображения alt.

Добавим в объект с опциями data новое свойство altText:

altText: "A pair of socks"

Привяжем соответствующие данные к атрибуту alt, приведя код элемента <img> к такому виду:

<img v-bind:src="image" v-bind:alt="altText" />

Здесь, как и в случае с атрибутом src, используется конструкция, состоящая из v-bind, двоеточия и имени атрибута (alt).

Теперь, если в данных экземпляра Vue изменятся свойства image или altText, в соответствующие атрибуты элемента <img> попадут обновлённые данные. При этом связь атрибутов элемента и данных, хранящихся в экземпляре Vue, не нарушится.

Этот приём постоянно используется при разработке Vue-приложений. Из-за этого существует сокращённый вариант записи конструкции v-bind:имяатрибута. Он выглядит как :имяатрибута. Если использовать этот приём при написании кода тега <img>, то получится следующее:

<img :src="image" />

Это просто и удобно. Данный приём улучшает чистоту кода.

Практикум


Добавьте на страницу ссылку (элемент <a>) с текстом More products like this. В объекте data создайте свойство link, содержащее ссылку https://www.amazon.com/s/ref=nb_sb_noss?url=search-alias%3Daps&field-keywords=socks. Свяжите, используя директиву v-bind, свойство link с атрибутом href элемента <a>.

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

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

Итоги


Вот что мы сегодня изучили:

  • Данные, хранящиеся в экземпляре Vue, можно привязывать к HTML-атрибутам.
  • Для привязки данных к атрибутам используется директива v-bind. Сокращённая запись этой директивы выглядит как двоеточие (:).
  • Имя атрибута, которое идёт за двоеточием, указывает на атрибут, к которому осуществляется привязка данных.
  • В качестве значения атрибута, указываемого в кавычках, используется имя ключа, по которому можно найти данные, подключаемые к атрибуту.

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

Подробнее..

Перевод Vue.js для начинающих, урок 4 рендеринг списков

15.07.2020 16:21:33 | Автор: admin
Сегодня, в четвёртом уроке учебного курса по Vue, мы поговорим о том, как выводить на страницу списки элементов.



Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг

Цель урока


Нам нужно вывести в карточке товара дополнительные сведения о нём. Эти сведения должны быть представлены в виде списка, содержащего следующее:

  • 80% cotton
  • 20% polyester
  • Gender-neutral

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


Начнём работу с такого HTML-кода (файл index.html):

<div class="product"><div class="product-image"><img :src="image" /></div><div class="product-info"><h1>{{ product }}</h1><p v-if="inStock">In stock</p><p v-else>Out of Stock</p></div></div>

Вот как будет выглядеть объект data, используемый при создании экземпляра Vue в main.js:

data: {product: "Socks",image: "./assets/vmSocks-green.jpg",inStock: true,details: ['80% cotton', '20% polyester', 'Gender-neutral']}

Здесь появилось новое свойство массив details.

Задача


Необходимо вывести на странице содержимое массива details. Для этого требуется найти ответы на вопросы о том, как перебрать массив, и о том, как визуализировать его данные.

details: ['80% cotton', '20% polyester', 'Gender-neutral']

Решение задачи


Тут нам поможет ещё одна директива Vue v-for. Она позволяет перебирать массивы и выводить содержащиеся в них данные.

Добавим в index.html следующий код:

<ul><li v-for="detail in details">{{ detail }}</li></ul>

Благодаря этому на странице появится список дополнительных сведений о товаре.


Список на странице

Синтаксическая конструкция, используемая в кавычках вместе с директивой v-for, покажется знакомой тем, кто пользовался JavaScript-циклами for of или for in. Поговорим о том, как работает директива v-for.

Здесь мы используем существительное в единственном числе (detail) в качестве псевдонима для строковых значений, извлекаемых из массива. Затем мы пишем in и указываем имя коллекции, которую перебираем (details). В двойных фигурных скобках указывается то, какие именно данные мы хотим выводить ({{ detail }}).

Так как конструкция v-for находится внутри элемента <li>, Vue выведет новый элемент <li> для каждого элемента массива details. Если бы директива v-for использовалась внутри элемента <div>, тогда для каждого элемента массива выводился бы элемент <div>, визуализирующий значение этого элемента массива.

Директиву v-for можно представить себе в виде конвейера, на котором имеется манипулятор. Он берёт элементы коллекции, по одному за раз, и собирает список.

image
Директива v-for похожа на конвейер

Рассмотрим ещё один пример применения v-for, более сложный. Здесь мы будем выводить в элементе <div> данные, хранящиеся в массиве объектов.

Перебор массива объектов


Карточка товара, разработкой которой мы занимаемся, нуждается в возможности выводить сведения о разных вариантах одного и того же товара. Эти сведения содержатся в массиве объектов variants, который хранится в объекте с данными data. Как перебрать этот массив объектов для вывода данных?

Вот массив, о котором идёт речь:

variants: [{variantId: 2234,variantColor: 'green'},{variantId: 2235,variantColor: 'blue'}]

В объектах, которые содержатся в данном массиве, имеется название цвета и идентификатор варианта товара.

Выведем эти данные на странице:

<div v-for="variant in variants"><p>{{ variant.variantColor }}</p></div>


Список вариантов товара

Здесь нам нужно вывести на страницу лишь название цвета, соответствующее разным вариантам товара. Поэтому мы, обращаясь к элементам массива, используем точечную нотацию. Если бы мы, в фигурных скобках, написали {{ variant }}, то на страницу вывелся бы весь объект.

Обратите внимание на то, что при рендеринге подобных элементов рекомендуется использовать специальный атрибут key. Это позволяет Vue отслеживать идентичность элементов. Добавим такой атрибут в наш код, используя в качестве его значения уникальное свойство variantId объектов, содержащих сведения о вариантах товара:

<div v-for="variant in variants" :key="variant.variantId"><p>{{ variant.variantColor }}</p></div>

Практикум


Добавьте в объект с данными массив sizes, содержащий сведения о размерах носков, и, используя директиву v-for, выведите данные из этого массива на странице в виде списка.

Массив sizes может выглядеть так:

sizes: ['S', 'M', 'L', 'XL', 'XXL', 'XXXL']

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

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

Итоги


Сегодня мы узнали следующее:

  • Директива v-for позволяет перебирать массивы для вывода содержащихся в них данных.
  • В конструкции v-for для доступа к элементам массива используется псевдоним. Здесь же указывается и имя самого массива. Например, это может выглядеть так: v-for=item in items.
  • При переборе массива объектов можно использовать точечную нотацию для доступа к свойствам объектов.
  • При использовании v-for рекомендуется назначать каждому выводимому элементу уникальный ключ.

Заглядываете ли вы в документацию Vue, занимаясь по этому курсу?

Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг

Подробнее..

Перевод Vue.js для начинающих, урок 5 обработка событий

21.07.2020 16:21:44 | Автор: admin
Сегодня, в пятом уроке курса по Vue.js для начинающих, речь пойдёт о том, как обрабатывать события.



Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг
Vue.js для начинающих, урок 4: рендеринг списков

Цель урока


Первая цель урока заключается в том, чтобы в карточке товара появилась бы кнопка, нажатия на которую увеличивают количество товара в корзине.

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

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


В файле проекта index.html будет присутствовать следующий код:

<div id="app"><div class="product"><div class="product-image"><img :src="image" /></div><div class="product-info"><h1>{{ product }}</h1><p v-if="inStock">In stock</p><p v-else>Out of Stock</p><ul><li v-for="detail in details">{{ detail }}</li></ul><div v-for="variant in variants" :key="variant.variantId"><p>{{ variant.variantColor }}</p></div></div></div></div>

Вот содержимое main.js:

var app = new Vue({el: '#app',data: {product: "Socks",image: "./assets/vmSocks-green.jpg",inStock: true,details: ['80% cotton', '20% polyester', 'Gender-neutral'],variants: [{variantId: 2234,variantColor: "green"},{variantId: 2235,variantColor: "blue"}]}})

Задача


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

Решение


Для начала добавим, в main.js, в объект data, новое свойство, которое будет символизировать количество товара в корзине:

cart: 0

Теперь, в index.html, добавим элемент <div>, описывающий корзину. В этом элементе будет использован тег <p>, с помощью которого на страницу будет выводиться число, хранящееся в свойстве cart:

<div class="cart"><p>Cart({{ cart }})</p></div>

Ещё мы создадим в коде index.html кнопку, которая позволяет добавлять товар в корзину:

<button v-on:click="cart += 1">Add to cart</button>

Здесь обратите внимание на то, что для инкрементирования значения, хранящегося в cart, мы используем директиву v-on.


Страница с корзиной и с кнопкой для добавления товара в корзину

Если теперь нажать на кнопку количество товара в корзине увеличится на 1.

Как всё это работает?

Давайте разберёмся в представленной здесь конструкции. Использование директивы v-on сообщает Vue о том, что мы хотим прослушивать события, происходящие с кнопкой. Потом идёт двоеточие, после которого указывается то, какое конкретно событие нас интересует. В данном случае это событие click. В кавычках записано выражение, которое добавляет 1 к значению, хранящемуся в cart. Это происходит при каждом щелчке по кнопке.

Это простой, но не вполне реалистичный пример. Вместо того, чтобы указывать в кавычках выражение cart += 1, давайте сделаем так, чтобы щелчок по кнопке вызывал бы метод, который будет увеличивать значение, хранящееся в cart. Вот как это выглядит:

<button v-on:click="addToCart">Add to cart</button>

Как видите, здесь addToCart это имя метода, который будет вызван при возникновении события click. Но сам метод мы пока не объявили, поэтому давайте сделаем это прямо сейчас, оснастив им наш экземпляр Vue.

Тут используется механизм, очень похожий на тот, который мы уже применяем для хранения данных. А именно, речь идёт о том, что у объекта с опциями, используемого при создании экземпляра Vue, может быть необязательное свойство, носящее имя methods, в котором содержится объект с методами. В нашем случае это будет всего один метод addToCart:

methods: {addToCart() {this.cart += 1}}

Теперь, когда мы щёлкаем по кнопке, вызывается метод addToCart, который и увеличивает значение cart, выводящееся в теге <p>.

Продолжим разбор того, что здесь происходит.

Кнопка прослушивает события click благодаря директиве v-on, которая вызывает метод addToCart. Этот метод находится в свойстве methods экземпляра Vue. В теле функции содержится инструкция, добавляющая 1 к значению this.cart. Так как this хранит ссылку на то место, где хранятся данные экземпляра Vue, в котором мы находимся, функция добавляет 1 к значению cart. А this.cart это то же самое, что и свойство cart, объявленное в свойстве data объекта с опциями.

Если бы мы просто написали бы в теле функции что-то вроде cart += 1, то мы столкнулись бы с сообщением об ошибке cart is not defined. Именно поэтому мы используем конструкцию this.cart и обращаемся к cart из экземпляра Vue, используя this.

Возможно, вы сейчас задаётесь вопросом о том, что сейчас мы просто увеличиваем количество товаров в корзине, но самого товара в корзину не добавляем. Может, мы что-то делаем не так? Это правильный вопрос. Мы реализуем соответствующий функционал позже, в одном из следующих уроков.

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

Для начала давайте расширим объекты массива variants из объекта data, добавив туда свойство variantImage, хранящее путь к изображению нужного варианта товара. Приведём соответствующий раздел файла main.js к такому виду:

variants: [{variantId: 2234,variantColor: "green",variantImage: "./assets/vmSocks-green.jpg"},{variantId: 2235,variantColor: "blue",variantImage: "./assets/vmSocks-blue.jpg"}],

Теперь каждому варианту товара, зелёным и синим носкам, назначено собственное изображение.

Задача


Нужно, чтобы, по наведению мыши на название цвета варианта носков, в поле, где выводится изображение товара, вывелось бы изображение variantImage для соответствующего цвета.

Решение


Тут нам снова пригодится директива v-on. Но в этот раз мы воспользуемся сокращённым вариантом её записи, который выглядит как @. А прослушивать будем событие mouseover.

Вот соответствующий код в index.html:

<div v-for="variant in variants" :key="variant.variantId"><p @mouseover="updateProduct(variant.variantImage)">{{ variant.variantColor }}</p></div>

Обратите внимание на то, что мы передаём методу updateProduct, в виде аргумента, variant.variantImage.

Создадим этот метод в main.js:

updateProduct(variantImage) {this.image = variantImage}

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

Но тут мы обновляем значение, хранящееся в image. А именно, в image записывается то, что хранится в variantImage того варианта товара, на который наведён указатель мыши. Соответствующее значение передаётся функции updateProduct из самого обработчика события, находящегося в index.html:

<p @mouseover="updateProduct(variant.variantImage)">

Другими словами, теперь метод updateProduct готов к вызову с параметром variantImage.

Когда вызывается этот метод, variant.variantImage передаётся ему в виде variantImage и используется для обновления значения, хранящегося в this.image. Мы, по аналогии с ранее рассмотренной конструкцией this.cart, можем сказать, что this.image это то же самое, что image. В результате значение, хранящееся в image, теперь динамически обновляется в соответствии с данными варианта товара, на который наведён указатель мыши.

Синтаксис ES6


Здесь мы, создавая методы, пользовались такими конструкциями:

updateProduct(variantImage) {this.image = variantImage}

Это сокращённый вариант описания методов, который появился в ES6. Более старый вариант записи подобных конструкций выглядит так:

updateProduct: function(variantImage) {this.image = variantImage}

Практикум


Создайте кнопку и соответствующий метод, которые позволят уменьшать значение, хранящееся в cart.

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

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

Итоги


Подведём итоги сегодняшнего занятия:

  • Для организации реакции элемента на события используется директива v-on.
  • Сокращённый вариант директивы v-on выглядит как @.
  • При использовании v-on можно указать тип прослушиваемого события:

    • click
    • mouseover
    • любое событие DOM
  • Директива v-on может вызывать методы.
  • Метод, вызываемый с помощью v-on, может принимать аргументы.
  • Ключевое слово this содержит ссылку на то место, где хранятся данные текущего экземпляра Vue. Его использование позволяет работать с данными экземпляра, а так же с методами, объявленными в экземпляре.

Получилось ли у вас домашнее задание к этому уроку?

Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг
Vue.js для начинающих, урок 4: рендеринг списков

Подробнее..

Перевод Vue.js для начинающих, урок 6 привязка классов и стилей

24.07.2020 16:16:55 | Автор: admin
Сегодня, в шестом уроке курса по Vue, мы поговорим о том, как динамически стилизовать HTML-элементы, привязывая данные к их атрибутам style и привязывая к элементам классы.



Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг
Vue.js для начинающих, урок 4: рендеринг списков
Vue.js для начинающих, урок 5: обработка событий

Цель урока


Первой целью данного урока будет использование цвета, соответствующего вариантам товаров, для настройки свойства background-color элементов <div>, выводящих сведения об этих вариантах. Так как вариантам товара соответствуют цвета green и blue, нам нужно, чтобы один элемент <div> имел бы зелёный фоновый цвет, а второй синий.

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

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


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

<div id="app"><div class="product"><div class="product-image"><img :src="image" /></div><div class="product-info"><h1>{{ product }}</h1><p v-if="inStock">In stock</p><p v-else>Out of Stock</p><ul><li v-for="detail in details">{{ detail }}</li></ul><div v-for="variant in variants" :key="variant.variantId"><p @mouseover="updateProduct(variant.variantImage)">{{ variant.variantColor }}</p></div><button v-on:click="addToCart">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",image: "./assets/vmSocks-green.jpg",inStock: true,details: ['80% cotton', '20% polyester', 'Gender-neutral'],variants: [{variantId: 2234,variantColor: "green",variantImage: "./assets/vmSocks-green.jpg"},{variantId: 2235,variantColor: "blue",variantImage: "./assets/vmSocks-blue.jpg"}],cart: 0},methods: {addToCart() {this.cart += 1},updateProduct(variantImage) {this.image = variantImage}}})

Задача


В предыдущем уроке мы создали обработчик событий, который меняет изображение товара, основываясь на том, на какой элемент <p> был наведён указатель мыши. Вместо того чтобы выводить название цвета в элементе <p>, мы хотели бы использовать этот цвет для настройки свойства background-color соответствующего элемента <div>. При таком подходе, вместо того, чтобы наводить мышь на тексты, мы сможем наводить её на цветные квадраты, что приведёт к выводу на странице изображения товара, цвет которого соответствует цвету, показанному в квадрате.

Решение


Для начала давайте добавим к элементу <div> класс color-box, который задаёт его ширину, высоту и внешний верхний отступ. Так как мы, даже сделав это, продолжаем выводить в элементах <div> слова green и blue, мы можем взять названия цветов, хранящихся в объектах, описывающих варианты товара, и использовать эти названия при привязке стиля к атрибуту style. Вот как это выглядит:

<divclass="color-box"v-for="variant in variants":key="variant.variantId":style="{ backgroundColor:variant.variantColor }"><p @mouseover="updateProduct(variant.variantImage)">{{ variant.variantColor }}</p></div>

Обратите внимание на вторую и пятую строки этого кода. Здесь мы добавляем к элементу <div> класс color-box и привязываем к нему встроенный стиль. Встроенный стиль здесь используется для динамической настройки свойства background-color элементов <div>. Цвет для фона элементов берётся из variant.variantColor.


Стилизованные элементы <div> и выводимые на них надписи

Теперь, когда элемент <div> стилизован с использованием variantColor, нам больше не нужно выводить в нём название цвета. Поэтому мы можем избавиться от тега <p> и переместить конструкцию @mouseover=updateProduct(variant.variantImage) в сам элемент <div>.

Вот как будет выглядеть код после внесения в него вышеописанных изменений:

<divclass="color-box"v-for="variant in variants":key="variant.variantId":style="{ backgroundColor:variant.variantColor }"@mouseover="updateProduct(variant.variantImage)"></div>


Стилизованные элементы <div> без текста

Теперь при наведении мыши на синий квадрат на странице выводится изображение синих носков. А при наведении мыши на зелёный квадрат изображение зелёных носков. Красота!

Разобравшись с привязкой стилей, поговорим о привязке классов.

Задача


Сейчас в наших данных есть следующее:

inStock: true,

Когда свойство inStock принимает значение false, нам нужно запретить посетителям сайта щёлкать по кнопке Add to Cart, так как на складе нет товара, а значит, его нельзя добавить в корзину. К нашей удаче, существует специальный HTML-атрибут, носящий имя disabled, с помощью которого можно отключить кнопку.

Если вспомнить материал второго урока, то окажется, что мы можем воспользоваться техникой привязки атрибутов для добавления к элементу атрибута disabled тогда, когда inStock равняется false, или, скорее, в случае, когда это значение не является истинным (!inStock). Перепишем код кнопки:

<buttonv-on:click="addToCart":disabled="!inStock">Add to cart</button>

Теперь, в том случае, если в inStock записано false, кнопка работать не будет. Но её внешний вид не изменится. Другими словами, кнопка всё ещё будет выглядеть так, будто на неё можно нажать, несмотря на то, что на самом деле нажимать на неё бессмысленно.


Отключённая кнопка выглядит так же, как обычная, но щёлкать по ней бессмысленно

Решение


Тут мы поступим, действуя по той же схеме, по которой действовали, привязывая inStock к атрибуту disabled. А именно, будем привязывать класс disabledButton к нашей кнопке в случаях, когда inStock хранит false. При таком подходе, если по кнопке будет бессмысленно щёлкать, то и выглядеть она будет соответственно.

<buttonv-on:click="addToCart":disabled="!inStock":class="{ disabledButton: !inStock }">Add to cart</button>


Отключённая кнопка выглядит так, как нужно

Как видите, теперь кнопка становится серой в том случае, если inStock равняется false.

Давайте разберёмся в том, что здесь происходит.

Взгляните на эту строчку:

:class="{ disabledButton: !inStock }"

Здесь мы используем сокращённый вариант записи директивы v-bind (:) для организации привязки данных к атрибуту class кнопки. В фигурных скобках мы определяем присутствие класса disabledButton на основании истинности свойства inStock.

Другими словами, когда товара на складе нет (!inStock), к кнопке добавляется класс disabledButton. Так как этот класс задаёт серый фоновый цвет кнопки, кнопка становится серой.

Замечательно! Только что мы скомбинировали наши новые знания, касающиеся привязки классов, со знаниями о привязке атрибутов, и смогли отключить кнопку и делать её серой в том случае, если inStock равняется false.

Дополнительные сведения


К элементу можно привязывать объект классов или массив классов:

<div :class="classObject"></div><div :class="[activeClass, errorClass]"></div>

Практикум


Когда в inStock записано значение false, нужно привязать к тегу <p>, выводящему текст Out of Stock, класс, который добавляет к элементу стиль text-decoration: line-through, перечёркивая текст.

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

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

Итоги


Вот самое важное из того, что мы сегодня изучили:

  • Данные можно привязывать к атрибуту элементов style.
  • Данные можно привязывать к атрибуту элементов class.
  • При организации привязки классов можно пользоваться выражениями, от вычисления которых зависит то, будет ли соответствующий класс привязан к элементу.


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

Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг
Vue.js для начинающих, урок 4: рендеринг списков
Vue.js для начинающих, урок 5: обработка событий

Подробнее..

Перевод Vue.js для начинающих, урок 7 вычисляемые свойства

28.07.2020 18:07:12 | Автор: admin
Сегодня, в седьмом уроке курса по Vue, мы поговорим о вычисляемых свойствах. Эти свойства экземпляра Vue не хранят значения, а вычисляют их.



Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг
Vue.js для начинающих, урок 4: рендеринг списков
Vue.js для начинающих, урок 5: обработка событий
Vue.js для начинающих, урок 6: привязка классов и стилей

Цель урока


Нашей основной целью является вывод данных, описываемых свойствами объекта с данными brand и product, в виде единой строки.

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


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

<div id="app"><div class="product"><div class="product-image"><img :src="image" /></div><div class="product-info"><h1>{{ product }}</h1><p v-if="inStock">In stock</p><p v-else :class="{ outOfStock: !inStock }">Out of Stock</p><ul><li v-for="detail in details">{{ detail }}</li></ul><divclass="color-box"v-for="variant in variants":key="variant.variantId":style="{ backgroundColor:variant.variantColor }"@mouseover="updateProduct(variant.variantImage)"></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',image: './assets/vmSocks-green.jpg',inStock: true,details: ['80% cotton', '20% polyester', 'Gender-neutral'],variants: [{variantId: 2234,variantColor: 'green',variantImage: './assets/vmSocks-green.jpg'},{variantId: 2235,variantColor: 'blue',variantImage: './assets/vmSocks-blue.jpg'}],cart: 0},methods: {addToCart() {this.cart += 1},updateProduct(variantImage) {this.image = variantImage}}})

Обратите внимание на то, что в объект с данными добавлено новое свойство с именем brand.

Задача


Нам надо, чтобы то, что хранится в brand и в product, было бы скомбинировано в одну строку. Другими словами, нам нужно вывести в теге <h1> текст Vue Mastery Socks, а не просто Socks. Для решения этой задачи нужно задаться вопросом о том, как можно конкатенировать два строковых значения, хранящихся в экземпляре Vue.

Решение задачи


Мы для решения этой задачи воспользуемся вычисляемыми свойствами. Так как эти свойства не хранят значения, а вычисляют их, давайте добавим в объект с опциями, используемый при создании экземпляра Vue, свойство computed и создадим вычисляемое свойство с именем title:

computed: {title() {return this.brand + ' ' + this.product;}}

Полагаем, тут всё устроено очень просто и понятно. Когда вызывается метод title(), он выполняет конкатенацию строк brand и product, после чего возвращает полученную в результате новую строку.

Теперь нам осталось лишь вывести title в теге <h1> нашей страницы.

Сейчас этот тег выглядит так:

<h1>{{ product }}</h1>

А теперь мы сделаем его таким:

<h1>{{ title }}</h1>

Взглянем на страницу и проверим работоспособность того, что мы только что сделали.


Заголовок страницы изменился

Как видно, в заголовке выводится Vue Mastery Socks, а это значит, что мы всё сделали правильно.

Мы взяли два значения из данных экземпляра Vue и создали на их основе новое значение. Если значение brand когда-нибудь будет обновлено, например в это свойство окажется записанной строка Vue Craftery, то вносить какие-то изменения в код вычисляемого свойства не потребуется. Это свойство будет продолжать возвращать корректную строку, которая теперь будет выглядеть как Vue Craftery Socks. В вычисляемом свойстве title всё ещё будет использоваться свойство brand, так же, как и раньше, но теперь в brand будет записано новое значение.

Это был очень простой пример, но пример, вполне применимый на практике. Давайте теперь рассмотрим более сложный вариант использования вычисляемых свойств.

Более сложный пример


Сейчас мы обновляем изображение, выводимое на странице, используя метод updateProduct. Мы передаём ему variantImage, а затем записываем в свойство image то, что попало в метод после наведения мыши на соответствующий цветной квадрат. Соответствующий код выглядит так:

updateProduct(variantImage) {this.image = variantImage;}

Этот механизм работает нормально, но если нам понадобится, основываясь на том, на какой именно цветной квадрат наведена мышь, менять не только изображение, но и что-то ещё, это будет означать необходимость рефакторинга данного кода. Давайте этим и займёмся.

А именно, вместо того, чтобы хранить в данных свойство image, заменим его на свойство selectedVariant. Инициализируем его значением 0.

selectedVariant: 0,

Почему 0? Дело в том, что мы планируем устанавливать это свойство на основе индекса (index) элемента, над которым находится указатель мыши. Мы можем добавить индекс в конструкцию v-for:

<divclass="color-box"v-for="(variant, index) in variants":key="variant.variantId":style="{ backgroundColor:variant.variantColor }"@mouseover="updateProduct(variant.variantImage)"></div>

Обратите внимание на то, что там, где раньше была конструкция v-for=variant in variants, теперь находится код v-for=(variant, index) in variants.

Теперь, вместо того, чтобы передавать variant.variantImage в updateProduct, передадим в этот метод index:

@mouseover="updateProduct(index)"

Теперь займёмся кодом метода updateProduct. Здесь мы получаем индекс. И, вместо записи нового значения в this.image, запишем index в this.selectedVariant. То есть, в selectedVariant попадёт значение index, соответствующее тому квадрату, на который был наведён указатель мыши. Ещё мы, в целях отладки, поместим в этот метод команду для логирования значения index.

updateProduct(index) {this.selectedVariant = index;console.log(index);}

Если сейчас обновить страницу и открыть консоль инструментов разработчика, мы можем убедиться в том, что при наведении мыши на квадраты в консоль попадают значения 0 и 1.


Проверка работоспособности созданного нами механизма

Однако изображение теперь на странице не выводится. В консоли появляется предупреждение.


Предупреждение, выводимое в консоль

Дело тут в том, что мы удалили свойство image, заменив его свойством selectedValue, но это свойство в нашем приложении всё ещё используется. Давайте исправим проблему, вернув image в экземпляр Vue, но на этот раз в виде вычисляемого свойства. Соответствующий код будет выглядеть так:

image() {return this.variants[this.selectedVariant].variantImage;}

Здесь мы возвращаем свойство variantImage элемента массива this.variants[this.selectedVariant]. В качестве индекса, по которому осуществляется доступ к элементу массива, используется свойство this.selectedVariant, которое равняется 0 или 1. Это, соответственно, даёт нам доступ к первому или ко второму элементу массива.

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

Сейчас, когда мы подвергли рефакторингу код метода updateProduct, который теперь обновляет состояние свойства selectedVariant, мы можем поработать и с другими данными, хранящимися в объектах из массива variants, с такими, как поле variantQuantity, которое мы сейчас добавим в объекты:

variants: [{variantId: 2234,variantColor: 'green',variantImage: './assets/vmSocks-green.jpg',variantQuantity: 10},{variantId: 2235,variantColor: 'blue',variantImage: './assets/vmSocks-blue.jpg',variantQuantity: 0}],

Давайте избавимся от обычного свойства inStock и, как и при работе со свойством image, создадим новое вычисляемое свойство с тем же именем, значение, возвращаемое которым, будет основываться на selectedVariant и variantQuantity:

inStock(){return this.variants[this.selectedVariant].variantQuantity}

Это свойство очень похоже на вычисляемое свойство image. Но теперь мы берём из соответствующего объекта не свойство variantImage, а свойство variantQuantity.

Если теперь навести указатель мыши на квадрат, количество товара, соответствующее которому, равняется нулю, в inStock попадёт 0, а 0 является в JavaScript значением, приводимым к логическому значению false. Из-за этого на странице будет выведено сообщение Out of Stock.

Обратите внимание на то, что кнопка тоже, как и ранее, правильно реагирует на установку inStock в 0.


Кнопка и надпись зависят от количества товара каждого вида

Почему всё продолжает правильно работать? Дело в том, что inStock всё ещё используется для привязки класса disableButton к нашей кнопке. Единственное различие нового варианта приложения и его предыдущего варианта заключается в том, что теперь inStock это вычисляемое, а не обычное свойство.

Дополнительные сведения о вычисляемых свойствах


Вычисляемые свойства кешируются. То есть результаты вычисления этих свойств сохраняются в системе до тех пор, пока не изменятся данные, от которых зависят эти результаты. В результате, когда изменится variantQuantity, кеш будет очищен. А когда к inStock обратятся в следующий раз, свойство вернёт новый результат, который и будет помещён в кеш.

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

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

Практикум


Добавьте в объект с данными, используемый при создании экземпляра Vue, новое логическое свойства onSale. Оно будет указывать на то, проводится ли распродажа. Создайте вычисляемое свойство sale, которое, на основе brand, product и onSale формирует строку, сообщающую о том, проводится ли сейчас распродажа или нет. Выведите эту строку в карточке товара.

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

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

Итоги


На этом занятии мы познакомились с вычисляемыми свойствами. Вот самое важное из того, что мы о них узнали:

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


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

Vue.js для начинающих, урок 1: экземпляр Vue
Vue.js для начинающих, урок 2: привязка атрибутов
Vue.js для начинающих, урок 3: условный рендеринг
Vue.js для начинающих, урок 4: рендеринг списков
Vue.js для начинающих, урок 5: обработка событий
Vue.js для начинающих, урок 6: привязка классов и стилей

Подробнее..

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

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

Подробнее..

Перевод Vue.js для начинающих, урок 9 пользовательские события

04.08.2020 16:15:07 | Автор: admin
На предыдущем уроке нашего курса по Vue вы узнали о том, как создавать компоненты, и о том, как передавать данные от родительских сущностей дочерним с использованием механизма входных параметров (props). А что если данные нужно передавать в обратном направлении? Сегодня, в девятом уроке, вы узнаете о том, как наладить двустороннюю связь между компонентами разного уровня.



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: компоненты

Цель урока


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

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


В файле index.html учебного проекта сейчас находится такой код:

<div id="app"><product :premium="premium"></product></div>

Вот содержимое файла main.js:

Vue.component('product', {props: {premium: {type: Boolean,required: true}},template: `<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>`,data() {return {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;},shipping() {if (this.premium) {return "Free";} else {return 2.99}}}})var app = new Vue({el: '#app',data: {premium: true}})

Задача


Теперь, когда product представлен самостоятельным компонентом, то, что код, имеющий отношение к корзине, находится в product, смысла не имеет. Если у каждого товара будет своя корзина, за состоянием которой нам нужно наблюдать, в нашем приложении заведётся большой беспорядок. Вместо этого мы хотели бы, чтобы корзина существовала бы на уровне корневого экземпляра Vue. Так же нам нужно, чтобы компонент product сообщал бы корневому экземпляру Vue о добавлении товаров в корзину, то есть о нажатиях на кнопку Add to cart.

Решение


Переместим данные, имеющие отношение к корзине, обратно в корневой экземпляр Vue:

var app = new Vue({el: '#app',data: {premium: true,cart: 0}})

Далее перенесём шаблон корзины обратно в index.html, приведя его код к такому виду:

<div id="app"><div class="cart"><p>Cart({{ cart }})</p></div><product :premium="premium"></product></div>

Теперь, если открыть страницу приложения в браузере и нажать на кнопку Add to cart, ничего, как и ожидается, не произойдёт.


Нажатие на кнопку Add to cart пока ни к чему не приводит

А что должно происходить по нажатию на эту кнопку? Нам нужно, чтобы при нажатии на неё корневой экземпляр Vue получал бы уведомление, которое вызывало бы метод, приводящий корзину в актуальное состояние, то есть, обновляющий значение, которое хранится в cart.

Для того чтобы этого добиться, давайте сначала перепишем код метода addToCart компонента product.

Сейчас он выглядит так:

addToCart() {this.cart += 1;},

Приведём его к такому виду:

addToCart() {this.$emit('add-to-cart');},

Что всё это значит?

А значит это вот что. Когда вызывается метод addToCart, генерируется пользовательское событие с именем add-to-cart. Другими словами когда нажимают на кнопку Add to cart, вызывается метод, генерирующий событие, сообщающее о том, что только что была нажата кнопка (то есть что только что произошло событие, вызванное нажатием кнопки).

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

<product :premium="premium" @add-to-cart="updateCart"></product>

Тут мы пользуемся конструкцией вида @add-to-card аналогично тому, как пользуемся конструкцией :premium. Но если :premium это трубопровод, по которому можно передавать данные дочернему компоненту от родительского, то @add-to-cart можно сравнить с радиоприёмником родительского компонента, который принимает от дочернего компонента сведения о том, что была нажата кнопка Add to cart. Так как радиоприёмник находится в теге <product>, вложенном в <div id="app">, это значит, что при поступлении сведений о нажатии на Add to cart будет вызван метод updateCart, находящийся в корневом экземпляре Vue.

Код @add-to-cart=updateCart в переводе на обычный язык выглядит так: Когда услышишь, что произошло событие add-to-cart, вызови метод updateCart.

Этот метод, который теперь будет объявлен в объекте с опциями, используемом при создании экземпляра Vue, вы, точно, уже где-то видели:

methods: {updateCart() {this.cart += 1;}}

Собственно говоря, это точно такой же метод, который использовался раньше в product. Но теперь он находится в корневом экземпляре Vue и вызывается по нажатию на кнопку Add to cart.


Кнопка снова работает

При нажатии на кнопку, находящуюся в компоненте product, вызывается метод addToCart, который генерирует событие. Корневой экземпляр Vue, слушая радио, узнаёт о том, что данное событие произошло и вызывает метод updateCart, который увеличивает число, хранящееся в cart.

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

Данные, передаваемые в событии, можно описать в виде второго аргумента, передаваемого $emit в коде метода addToCart компонента product:

this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);

Теперь в событии передаётся идентификатор (variantId) товара, который пользователь хочет добавить в корзину. Это значит, что мы, вместо того чтобы просто увеличивать количество товара в корзине, можем пойти дальше и хранить в корзине более подробные сведения о добавленных в неё товарах. Для этого сначала преобразуем корзину в массив, записав пустой массив в cart:

cart: []

Далее перепишем метод updateCart. Во-первых он теперь будет принимать id тот самый идентификатор товара, который передаётся теперь в событии, во-вторых он теперь будет помещать то, что получил, в массив:

methods: {updateCart(id) {this.cart.push(id);}}

После однократного нажатия на кнопку в массив попадает идентификатор товара. Массив выводится на странице.


Массив с идентификатором товара выводится на странице

Нам не нужно выводить на странице весь массив. Нас устроит вывод количества товаров, добавленных в корзину, то есть в массив cart. Поэтому мы можем переписать код тега <p>, в котором выводятся сведения о количестве товаров, добавленных в корзину, так:

<p>Cart({{ cart.length }})</p>


На странице выводятся сведения о количестве товаров, добавленных в корзину

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

Практикум


Добавьте в проект кнопку, которая удаляет из массива cart товар, добавленный туда ранее. По нажатию на эту кнопку должно генерироваться событие, содержащее сведения об идентификаторе товара, который нужно убрать из корзины.

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

Итоги


Вот что вы сегодня узнали:

  • Компонент может сообщать родительской сущности о том, что в нём что-то произошло, пользуясь конструкцией $emit.
  • Родительский компонент может использовать обработчик события, заданный с использованием директивы v-on (или её сокращённой версии @), для организации реакции на события, генерируемые дочерними компонентами. Если событие происходит в родительском компоненте может быть вызван обработчик события.
  • Родительский компонент может пользоваться данными, переданными в событии, сгенерированном дочерним компонентом.

Если вы изучаете курс и дошли до этого урока просим рассказать о том, с какой целью вы занимаетесь, чего хотите достичь, освоив 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: компоненты

Подробнее..

Перевод Vue.js для начинающих, урок 11 вкладки, глобальная шина событий

11.08.2020 16:15:49 | Автор: admin
Сегодня, в 11 уроке, который завершает этот учебный курс по основам 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: компоненты
Vue.js для начинающих, урок 9: пользовательские события
Vue.js для начинающих, урок 10: формы

Цель урока


Мы хотим, чтобы на странице приложения присутствовали бы вкладки, одна из которых позволяет посетителям писать отзывы о товарах, а другая просматривать существующие отзывы.

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


Вот как на данном этапе работы выглядит содержимое файла index.html:

<div id="app"><div class="cart"><p>Cart({{ cart.length }})</p></div><product :premium="premium" @add-to-cart="updateCart"></product></div>

В main.js имеется следующий код:

Vue.component('product', {props: {premium: {type: Boolean,required: true}},template: `<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, index) in details" :key="index">{{ detail }}</li></ul><divclass="color-box"v-for="(variant, index) in variants":key="variant.variantId":style="{ backgroundColor: variant.variantColor }"@mouseover="updateProduct(index)"></div><button@click="addToCart":disabled="!inStock":class="{ disabledButton: !inStock }">Add to cart</button></div><div><h2><font color="#3AC1EF">Reviews</font></h2><p v-if="!reviews.length">There are no reviews yet.</p><ul><li v-for="review in reviews"><p>{{ review.name }}</p><p>Rating: {{ review.rating }}</p><p>{{ review.review }}</p></li></ul></div><product-review @review-submitted="addReview"></product-review></div>`,data() {return {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}],reviews: []}},methods: {addToCart() {this.$emit('add-to-cart', this.variants[this.selectedVariant].variantId);},updateProduct(index) {this.selectedVariant = index;},addReview(productReview) {this.reviews.push(productReview)}},computed: {title() {return this.brand + ' ' + this.product;},image() {return this.variants[this.selectedVariant].variantImage;},inStock() {return this.variants[this.selectedVariant].variantQuantity;},shipping() {if (this.premium) {return "Free";} else {return 2.99}}}})Vue.component('product-review', {template: `<form class="review-form" @submit.prevent="onSubmit"><p v-if="errors.length"><b>Please correct the following error(s):</b><ul><li v-for="error in errors">{{ error }}</li></ul></p><p><label for="name">Name:</label><input id="name" v-model="name"></p><p><label for="review">Review:</label><textarea id="review" v-model="review"></textarea></p><p><label for="rating">Rating:</label><select id="rating" v-model.number="rating"><option>5</option><option>4</option><option>3</option><option>2</option><option>1</option></select></p><p><input type="submit" value="Submit"></p></form>`,data() {return {name: null,review: null,rating: null,errors: []}},methods: {onSubmit() {if(this.name && this.review && this.rating) {let productReview = {name: this.name,review: this.review,rating: this.rating}this.$emit('review-submitted', productReview)this.name = nullthis.review = nullthis.rating = null} else {if(!this.name) this.errors.push("Name required.")if(!this.review) this.errors.push("Review required.")if(!this.rating) this.errors.push("Rating required.")}}}})var app = new Vue({el: '#app',data: {premium: true,cart: []},methods: {updateCart(id) {this.cart.push(id);}}})

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


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

Задача


Сейчас отзывы и форма, которая используется для отправки отзывов, выводятся на странице рядом друг с другом. Это вполне рабочая структура. Но ожидается, что со временем на странице будет появляться всё больше и больше отзывов. Это значит, что пользователям будет удобнее взаимодействовать со страницей, на которой, по их выбору, выводится либо форма, либо список отзывов.

Решение задачи


Для того чтобы решить нашу задачу, мы можем добавить на страницу систему вкладок. Одна из них, с заголовком Reviews, будет выводить отзывы. Вторая, с заголовком Make a Review, будет выводить форму для отправки отзывов.

Создание компонента, реализующего систему вкладок


Начнём работу с создания компонента product-tabs. Он будет выводиться в нижней части визуального представления компонента product. Со временем он заменит собой тот код, который сейчас используется для вывода на странице списка отзывов и формы.

Vue.component('product-tabs', {template: `<div><span class="tab" v-for="(tab, index) in tabs" :key="index">{{ tab }}</span></div>`,data() {return {tabs: ['Reviews', 'Make a Review']}}})

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

В данных компонента имеется массив tabs, содержащий строки, которые мы используем в качестве заголовков вкладок. В шаблоне компонента применяется конструкция v-for, с помощью которой для каждого элемента массива tabs создаётся элемент <span>, содержащий соответствующую строку. То, что формирует этот компонент на данном этапе работы над ним, будет выглядеть так, как показано ниже.


Компонент product-tabs на начальном этапе работы над ним

Нам, для достижения наших целей, нужно знать о том, какая из вкладок является активной. Поэтому добавим в данные компонента свойство selectedTab. Будем динамически задавать значение этого свойства, пользуясь обработчиком событий, реагирующим на щелчки по заголовкам вкладок:

@click="selectedTab = tab"

В свойство будут записываться строки, соответствующие заголовкам вкладок.

То есть, если пользователь щёлкнет по вкладке Reviews, то в selectedTab будет записана строка Reviews. Если будет сделан щелчок по вкладке Make a Review, то в selectedTab попадёт строка Make a Review.

Вот как теперь будет выглядеть полный код компонента.

Vue.component('product-tabs', {template: `<div><ul><span class="tab"v-for="(tab, index) in tabs"@click="selectedTab = tab">{{ tab }}</span></ul></div>`,data() {return {tabs: ['Reviews', 'Make a Review'],selectedTab: 'Reviews' // устанавливается с помощью @click}}})

Привязка класса к активной вкладке


Пользователь, работая с интерфейсом, в котором используются вкладки, должен знать о том, какая вкладка является активной. Реализовать подобный механизм можно, воспользовавшись привязкой классов к элементам <span>, использующимся для вывода названий вкладок:

:class="{ activeTab: selectedTab === tab }"

Вот CSS-файл, в котором определён стиль использованного здесь класса activeTab. Вот как выглядит этот стиль:

.activeTab {color: #16C0B0;text-decoration: underline;}

А вот стиль класса tab:

.tab {margin-left: 20px;cursor: pointer;}

Если объяснить вышеприведённую конструкцию простым языком, то оказывается, что к вкладке применяется стиль, заданный для класса activeTab, в том случае, когда selectedTab равняется tab. Так как в selectedTab записывается название вкладки, по которой только что щёлкнул пользователь, стиль .activeTab будет применяться именно к активной вкладке.

Другими словами, когда пользователь щёлкнет по первой вкладке, в tab будет находиться Reviews, то же самое будет записано и в selectedTab. В результате к первой вкладке будет применён стиль .activeTab.

Теперь заголовки вкладок на странице будут выглядеть так, как показано ниже.


Выделенный заголовок активной вкладки

Судя по всему, на данном этапе всё работает так, как ожидается, поэтому мы можем идти дальше.

Работа над шаблоном компонента


Теперь, когда мы можем сообщить пользователю о том, какая именно вкладка является активной, можно продолжить работу над компонентом. А именно, речь идёт о доработке его шаблона, об описании того, что именно будет выводиться на странице при активации каждой из вкладок.

Подумаем о том, что надо показать пользователю в том случае, если он щёлкнет по вкладке Reviews. Это, понятно, отзывы о товаре. Поэтому переместим код вывода отзывов из шаблона компонента product в шаблон компонента product-tabs, разместив этот код ниже конструкции, используемой для вывода заголовков вкладок. Вот как теперь будет выглядеть шаблон компонента product-tabs:

template: `<div><ul><span class="tab":class="{ activeTab: selectedTab === tab }"v-for="(tab, index) in tabs"@click="selectedTab = tab">{{ tab }}</span></ul><div><p v-if="!reviews.length">There are no reviews yet.</p><ul><li v-for="review in reviews"><p>{{ review.name }}</p><p>Rating: {{ review.rating }}</p><p>{{ review.review }}</p></li></ul></div></div>`

Обратите внимание на то, что мы избавились от тега <h2><font color="#3AC1EF">, так как нам больше не нужно выводить заголовок Reviews над списком отзывов. Вместо этого заголовка будет выводиться заголовок соответствующей вкладки.

Но одного только переноса кода шаблона недостаточно для того чтобы обеспечить вывод отзывов. Массив reviews, данные которого используются для вывода отзывов, хранится в составе данных компонента product. Нам нужно передать этот массив в компонент product-tabs, используя механизм входных параметров компонента. Добавим в объект с опциями, используемый при создании product-tabs, следующее:

props: {reviews: {type: Array,required: false}}

Передадим массив reviews из компонента product в компонент product-tabs, воспользовавшись, в шаблоне product, следующей конструкцией:

<product-tabs :reviews="reviews"></product-tabs>

А теперь поразмыслим о том, что нужно вывести на странице в том случае, если пользователь щёлкнет по заголовку вкладки Make a Review. Это, конечно, форма для отправки отзывов. Для того чтобы подготовить проект к дальнейшей работе над ним перенесём код подключения компонента product-review из шаблона компонента product в шаблон product-tabs. Разместим следующий код ниже элемента <div>, используемого для вывода списка отзывов:

<div><product-review @review-submitted="addReview"></product-review></div>

Если взглянуть сейчас на страницу приложения, то окажется, что список отзывов и форма выводятся на ней ниже заголовков вкладок.


Промежуточный этап работы над страницей

При этом щелчки по заголовкам, хотя и приводят к их выделению, никак не влияют на другие элементы страницы. Далее, если попытаться воспользоваться формой, окажется, что она перестала нормально работать. Всё это вполне ожидаемые следствия изменений, внесённых нами в приложение. Продолжим работу и приведём наш проект в работоспособное состояние.

Вывод элементов страницы по условию


Теперь, когда мы подготовили основные элементы шаблона компонента product-tabs, пришло время создать систему, которая позволит выводить разные элементы страницы основываясь на том, по заголовку какой именно вкладки щёлкнул пользователь.

В данных компонента уже есть свойство selectedTab. Мы можем воспользоваться им в директиве v-show для организации условного рендеринга того, что относится к каждой из вкладок.

Так, к тегу <div>, содержащему код формирования списка отзывов, мы можем добавить такую конструкцию:

v-show="selectedTab === 'Reviews'"

Благодаря ей список отзывов будет выводиться тогда, когда будет активной вкладка Reviews.

Аналогично, к тегу <div>, в котором содержится код подключения компонента product-review, мы добавим следующее:

v-show="selectedTab === 'Make a Review'"

Это приведёт к тому, что форма будет выводиться только тогда, когда активна вкладка Make a Review.

Вот как теперь будет выглядеть шаблон компонента product-tabs:

template: `<div><ul><span class="tab":class="{ activeTab: selectedTab === tab }"v-for="(tab, index) in tabs"@click="selectedTab = tab">{{ tab }}</span></ul><div v-show="selectedTab === 'Reviews'"><p v-if="!reviews.length">There are no reviews yet.</p><ul><li v-for="review in reviews"><p>{{ review.name }}</p><p>Rating: {{ review.rating }}</p><p>{{ review.review }}</p></li></ul></div><div v-show="selectedTab === 'Make a Review'"><product-review @review-submitted="addReview"></product-review></div></div>`

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


Щелчки по вкладкам приводят к скрытию одних элементов и к отображению других

Но отправка отзывов с помощью формы всё так же не работает. Исследуем проблему и исправим её.

Решение проблемы с отправкой отзывов


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


Предупреждение в консоли

Очевидно, система не может обнаружить метод addReview. Что с ним случилось?

Для ответа на этот вопрос вспомним о том, что addReview это метод, который объявлен в компоненте product. Он должен вызываться в том случае, если компонент product-review (а это дочерний компонент компонента product) генерирует событие review-submitted:

<product-review @review-submitted="addReview"></product-review>

Именно так всё и работало до переноса вышеприведённого фрагмента кода в компонент product-tabs. А теперь дочерним компонентом product является компонент product-tabs, а product-review это теперь не ребёнок, компонента product, а его внук.

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

Рефакторинг кода проекта


Для того чтобы обеспечить связь внучатых компонентов с их бабушками и дедушками, или для того, чтобы наладить связь между компонентами одного уровня, нередко используют механизм, называемый глобальной шиной событий (global event bus).

Глобальная шина событий это канал связи, который можно использовать для передачи информации между компонентами. И это, на самом деле, просто экземпляр Vue, который создают, не передавая ему объект с опциями. Создадим шину событий:

var eventBus = new Vue()

Этот код попадёт на верхний уровень файла main.js.

Возможно, вам будет легче освоить эту концепцию, если вы представите шину событий в виде автобуса. Его пассажирами являются данные, которые одни компоненты отправляют другим. В нашем случае речь идёт о передаче одним компонентам сведений о событиях, сгенерированных другими компонентами. То есть, наш автобус будет ездить от компонента product-review к компоненту product, перевозя сведения о том, что форма был отправлена, и доставляя данные формы из product-review в product.

Сейчас в компоненте product-review, в методе onSubmit, есть такая строчка:

this.$emit('review-submitted', productReview)

Заменим её на следующую, воспользовавшись eventBus вместо this:

eventBus.$emit('review-submitted', productReview)

После этого больше не нужно прослушивать событие review-submitted компонента product-review. Поэтому изменим код этого компонента в шаблоне компонента product-tabs на такой:

<product-review></product-review>

Из компонента product теперь можно удалить метод addReview. Вместо него мы воспользуемся следующей конструкцией:

eventBus.$on('review-submitted', productReview => {this.reviews.push(productReview)})

О том, как именно применить её в компоненте, мы поговорим ниже, а пока в двух словах опишем то, что в ней происходит. Эта конструкция указывает на то, что когда eventBus генерирует событие review-submitted, нужно взять данные, передаваемые в этом событии (то есть productReview) и поместить их в массив reviews компонента product. Собственно говоря, это очень похоже на то, что до сих пор делалось в методе addReview, который нам больше не нужен. Обратите внимание на то, что в вышеприведённом фрагменте кода используется стрелочная функция. Этот момент достоин более подробного освещения.

Причины использования стрелочной функции


Здесь мы используем синтаксис стрелочных функций, который появился в ES6. Дело в том, что контекст стрелочной функции привязан к родительскому контексту. То есть когда мы, внутри этой функции, пользуемся ключевым словом this, оно равнозначно тому ключевому слову this, которое соответствует сущности, содержащей стрелочную функцию.

Этот код можно переписать и без использования стрелочных функций, но тогда понадобится организовать привязку this:

eventBus.$on('review-submitted', function (productReview) {this.reviews.push(productReview)}.bind(this))

Завершение работы над проектом


Мы почти достигли цели. Всё, что осталось сделать найти место для фрагмента кода, обеспечивающего реакцию на событие review-submitted. Таким местом в компоненте product может стать функция mounted:

mounted() {eventBus.$on('review-submitted', productReview => {this.reviews.push(productReview)})}

Что это за функция? Это хук жизненного цикла, который вызывается один раз после того, как компонент будет смонтирован в DOM. Теперь, после того, как компонент product будет смонтирован, он будет ожидать появления событий review-submitted. После того, как такое событие будет сгенерировано, в данные компонента будет добавлено то, что передано в этом событии, то есть productReview.

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


Форма работает так, как нужно

Шина событий это не лучшее решение для обеспечения связи компонентов


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

По мере того, как приложение растёт, в нём может очень пригодиться система управления состоянием, основанная на Vuex. Это паттерн и библиотека управления состоянием приложений.

Практикум


Добавьте в проект вкладки Shipping и Details, на которых, соответственно, выводится стоимость доставки покупок и сведения о товарах.

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

Итоги


Вот что вы узнали, изучив этот урок:

  • Для организации механизма вкладок можно воспользоваться средствами условного рендеринга.
  • Для передачи данных между компонентами приложений часто пользуются глобальной шиной событий, представленной отдельным экземпляром Vue, создаваемым без использования объекта с опциями.
  • Шина событий это не самое лучшее решение задачи организации обмена данными между компонентами. Для решения подобной задачи лучше всего воспользоваться специализированной библиотекой. Например Vuex.

Надеемся, что вы, изучив данный курс по 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: компоненты
Vue.js для начинающих, урок 9: пользовательские события
Vue.js для начинающих, урок 10: формы

Подробнее..

Онлайн-митап сообщества разработчиков MSK VUE.JS

20.07.2020 14:14:41 | Автор: admin
image

23 июля приглашаем на онлайн-митап сообщества разработчиков MSK VUE.JS.

В программе митапа:

  • Разработка конструктора отчетов c Cube.js;
  • 5 действенных техник оптимизации vue-приложений;
  • Решение проблем REST API при помощи GraphQL.

Зарегистрироваться

О митапе


Разработчик Cube.js Леонид Яковлев расскажет, как сделать конструктор отчетов при помощи cube.js на бэкенде. Подробно поговорит, что такое query builder, какие у него преимущества и покажет пример простого queryBuilder

Руководитель AFFINAGE Игорь Яковлев поделится собственными техниками, инсайтами и лайфхаками, как разрабатывать по-настоящему быстрые vue-приложения.

Разработчик Московской биржи Юлия Кузнецова расскажет, как GraphQL помогает решать проблемы REST API архитектуры и покажет, как это сделать на примере Vue Apollo.

Об экспертах


Леонид Яковлев разработчик в команде Developer Relations and Community фреймворка Cube.js.

Игорь Яковлев руководитель аутсорспродакшена по фронтенду AFFINAGE.

Юлия Кузнецова старший разработчик Московской биржи.

О MSK VUE.JS


MSK VUE.JS сообщество разработчиков Vue.js, которое регулярно проводит митапы, чтобы делиться опытом, обсуждать перспективы и строить комьюнити.

По теме:


Подробнее..

Из песочницы Переключение шаблона страниц во vuejs

04.08.2020 14:20:22 | Автор: admin

Иногда в приложении требуется шаблоны для различных страниц, чтобы не копировать код от компонента к компоненту, мы прописываем шаблон в основном компоненте (он же, обычно, App.vue) и с помощью <router-view> подставляем в него различные вьюшки.


image

Как мы видим, у различных страниц общая шапка. Сайт.

А что, если нам необходимо сделать несколько шаблонов для разных страниц или состояний приложения? Этим мы сейчас и займемся.


Первым делом нам необходимо Vue Js приложение с подключенным роутером.


Что из себя представляют шаблоны? Правильно, обычные компоненты, которые будут вызываться в зависимости от того или иного условия, будь то активная странница, статус аутентификации или (по бреду) время суток.


Подготовим основной компонент для работы с шаблонами.

Если вы создавали проект через Vue-Cli у вас он выглядит примерно так:


//ВАШ_ПРОЕКТ/src/App.vue<template>  <div id="app">    <div id="nav">      <router-link to="/">Home</router-link> |      <router-link to="/about">About</router-link>    </div>    <router-view/>  </div></template><style>#app {  font-family: Avenir, Helvetica, Arial, sans-serif;  -webkit-font-smoothing: antialiased;  -moz-osx-font-smoothing: grayscale;  text-align: center;  color: #2c3e50;}#nav {  padding: 30px;}#nav a {  font-weight: bold;  color: #2c3e50;}#nav a.router-link-exact-active {  color: #42b983;}</style>

Нам необходимо добавить новое вычисляемое свойство (Computed) в секцию script (если у вас ее нет, скопируйте ее из любого vue компонента).

Это свойство будет возвращать нам имя компонента-шаблона в зависимости от того или иного условия, в данном примере шаблон будет привязан к странице.


//ВАШ_ПРОЕКТ/src/App.vue...<script>    export default {        computed: {            //Это самое вычисляемое свойство            layout(){                //Вернем имя шаблона из роута или дефолтное значение                   //(шаблон для страниц, для которых мы не указали шаблон)                return this.$route.meta.layout || "default-layout"             }        }    }</script>...

Отредактируем секцию template добавим в нее динамический компонент, который будет меняться в зависимости от значения layout.


//ВАШ_ПРОЕКТ/src/App.vue<template>  <div id="app">      <!--Динамический компонент-->      <component :is="layout">          <router-view/>      </component>  </div></template>...

Теперь создадим пару шаблонов.

Для удобства будем хранить их в отдельной папке layouts.


скриншот структуры папкок

По значимости папка не сильно ушла от components или view, просто удобно.

Есть у Vue такой элемент slot, который позволит нам подставлять наши представления в компонент-шаблон. При отрисовке он заменяется на контент, который мы укажем при вызове компонента. Создадим три шаблона, пусть они будут одинаковыми, с разницей в цвете шапки и подвала, для наглядности.

Синий шаблон, дефолтный:



//ВАШ_ПРОЕКТ/src/layouts/Default.vue<template>    <div>        <header>            <ul class="nav">                <li><router-link class="link" to="/">Домашняя страница</router-link></li>                <li><router-link class="link" to="/page2">Страница 1</router-link></li>                <li><router-link class="link" to="/page3">Страница 2</router-link></li>                <li><router-link class="link" to="/page4">Страница 2</router-link></li>            </ul>        </header>        <section class="content">            <!--Элемент, который при отрисовке будет заменен на ваш view-->            <slot/>        </section>        <footer>        </footer>    </div></template><script>    export default {        name: "Default"    }</script><style scoped>    header{        background-color: blue;        height: 70px;        display: flex;        align-items: center;    }    footer{        background-color: blue;        height: 70px;    }    .content{        min-height: calc(100vh - 140px);    }    ul{        list-style: none;        margin: 0;        color: white;    }    li{        color: white;        display: inline;        margin: 0 5px;    }    .link{        color: white;        text-decoration: none;    }</style>

Зеленый шаблон:


//ВАШ_ПРОЕКТ/src/layouts/Green.vue<template>    <div>        <header>            <ul class="nav">                <li><router-link class="link" to="/">Домашняя страница</router-link></li>                <li><router-link class="link" to="/page2">Страница 1</router-link></li>                <li><router-link class="link" to="/page3">Страница 2</router-link></li>                <li><router-link class="link" to="/page4">Страница 2</router-link></li>            </ul>        </header>        <section class="content">            <!--Элемент, который при отрисовке будет заменен на ваш view-->            <slot/>        </section>        <footer>        </footer>    </div></template><script>    export default {        name: "green"    }</script><style scoped>    header{        background-color: green;        height: 70px;        display: flex;        align-items: center;    }    footer{        background-color: green;        height: 70px;    }    .content{        min-height: calc(100vh - 140px);    }    ul{        list-style: none;        margin: 0;        color: white;    }    li{        color: white;        display: inline;        margin: 0 5px;    }    .link{        color: white;        text-decoration: none;    }</style>

Красный шаблон:

//ВАШ_ПРОЕКТ/src/layouts/Red.vue<template>    <div>        <header>            <ul class="nav">                <li><router-link class="link" to="/">Домашняя страница</router-link></li>                <li><router-link class="link" to="/page2">Страница 1</router-link></li>                <li><router-link class="link" to="/page3">Страница 2</router-link></li>                <li><router-link class="link" to="/page4">Страница 2</router-link></li>            </ul>        </header>        <section class="content">            <!--Элемент, который при отрисовке будет заменен на ваш view-->            <slot/>        </section>        <footer>        </footer>    </div></template><script>    export default {        name: "Red"    }</script><style scoped>    header{        background-color: red;        height: 70px;        display: flex;        align-items: center;    }    footer{        background-color: red;        height: 70px;    }    .content{        min-height: calc(100vh - 140px);    }    ul{        list-style: none;        margin: 0;        color: white;    }    li{        color: white;        display: inline;        margin: 0 5px;    }    .link{        color: white;        text-decoration: none;    }</style>

Теперь зарегистрируем эти компоненты-шаблоны в нашем Vue.

//ВАШ_ПРОЕКТ/src/main.jsimport Vue from 'vue'import App from './App.vue'import router from './router'//Подключим файлы компонентовimport DefaultLayout from "./layouts/Default"import GreenLayout from "./layouts/Green"import RedLayout from "./layouts/Red"//И зарегистрируем их в нашем приложенииVue.component("default-layout", DefaultLayout)Vue.component("green-layout", GreenLayout)Vue.component("red-layout", RedLayout)Vue.config.productionTip = falsenew Vue({  router,  render: h => h(App)}).$mount('#app')

Отлично! Шаблоны готовы, давайте создадим четыре представления(страницы), два из которых будут отрисовываться с синим шаблоном, а два остальных с красным и зеленым. Они тоже будут одинаковыми, только с разными заголовками.


//ВАШ_ПРОЕКТ/src/views/page1.vue<template>    <div>        <h1>Синий шаблон</h1>    </div></template><script>    export default {        name: "Page1"    }</script><style scoped>

</style>//ВАШ_ПРОЕКТ/src/views/page2.vue<template>    <div>        <h1>Зеленый шаблон</h1>    </div></template><script>    export default {        name: "Page2"    }</script><style scoped></style>

//ВАШ_ПРОЕКТ/src/views/page3.vue<template>    <div>        <h1>Красный шаблон</h1>    </div></template><script>    export default {        name: "Page3"    }</script><style scoped></style>

//ВАШ_ПРОЕКТ/src/views/page4.vue<template>    <div>        <h1>Еще один синий шаблон</h1>    </div></template><script>    export default {        name: "Page4"    }</script><style scoped></style>

Теперь добавим роуты под наши страницы, и в мета-данных укажем какой шаблон им использовать при отрисовке.


import Vue from 'vue'import VueRouter from 'vue-router'//Подключим наши страницыimport Page1 from "../views/Page1"import Page2 from "../views/Page2"import Page3 from "../views/Page3"import Page4 from "../views/Page4"Vue.use(VueRouter)const routes = [    {        path: '/',        name: 'Home',        component: Page1        //Так, как синий шаблон у нас является дефолтным, его можно не указывать в мета-данных    },    {        path: '/page2',        name: 'Page2',        component: Page2,        //А вот это свойство как раз будет содержать название компонента-шаблона,        //который мы хотим использовать для данной страницы        meta:{            layout: "green-layout"        }    },    {        path: '/page3',        name: 'Page3',        component: Page3,        meta:{            layout: "red-layout"        }    },    {        path: '/page4',        name: 'Page4',        component: Page4,        //И снова ничего не указываем, чтобы задействовать дефолтный шаблон    }]const router = new VueRouter({    mode: 'history',    base: process.env.BASE_URL,    routes})export default router

Запускаем наше приложение и проверяем:

image

Целиком код можно посмотреть тут
Подробнее..

Категории

Последние комментарии

© 2006-2020, personeltest.ru