Vue.js для начинающих¶

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

Сегодня мы предлагаем вашему вниманию перевод учебного курса по Vue.js для начинающих.

Оригинал курса на английском вышел на сайте vuemastery.com, мы подготовили для вас перевод на русский.

Ссылка на оригинал перевода: https://habr.com/ru/companies/ruvds/articles/509700/

Эта статья представляет собой адаптацию курса под vue версии 3. Оригиральный курс был написан под vue 2, который уже не поддерживается разработчиком.

image.png

Предварительные требования¶

Предполагается, что тот, кто решит освоить этот курс, обладает знаниями в области базовых веб-технологий: HTML, CSS и JavaScript.

Страница, разработкой которой мы будем заниматься

Урок 1: экземпляр Vue¶

В этом уроке мы разберёмся с тем, как использовать Vue для вывода данных на веб-странице.

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

Мы начнём работу с очень простого HTML- и JavaScript-кода, расположенного в двух файлах.

Файл index.html:

In [ ]:
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Product App</title>
</head>
<body>

    <div id="app">
        <h1>Product Name</h1>
    </div>

    <script src="main.js"></script>
</body>
</html>

Файл main.js:

In [ ]:
var product = "Socks";

В этом курсе в качестве среды, в которой предлагается выполнять домашние задания, используется платформа codepen.io. Соответствующие заготовки оформлены в виде CodePen-проектов. Тем, кто проходит этот курс, рекомендуется самостоятельно запускать весь код, который они здесь встречают.

В интерфейсе CodePen есть три области для кода. Это, соответственно, поля HTML, CSS и JS. Код, введённый в полях CSS и JS, автоматически подключается к веб-странице, описанной в поле HTML. То есть — для того чтобы воссоздать в среде CodePen вышеприведённый пример нужно ввести в область HTML код, содержащийся в теге файла index.html без последней строчки, подключающей main.js, а в область JS — код main.js.

image.png

Начало экспериментов в CodePen

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

Задача¶

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

Решить эту задачу нам поможет фреймворк Vue.js. Вот официальное русскоязычное руководство по нему.

Первым шагом нашей работы с Vue будет подключение фреймворка к странице. Для этого внесём изменения в файл index.html, добавив в него, прямо над кодом подключения файла main.js, следующее:

In [ ]:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

Далее, в main.js, вводим следующий код, убрав из него объявление переменной product:

In [ ]:
const app = Vue.createApp({
    data: () => ({
        product: "Socks"
    })
});

app.mount('#app');

Теперь нужно связать DOM с данными экземпляра Vue. Делается это с использованием особой HTML-конструкции, с помощью синтаксиса Mustache, при применении которого используются двойные фигурные скобки:

In [ ]:
<div id="app">
    <h1>{{ product }}</h1>
</div>

JavaScript-выражение в фигурных скобках будет заменено на значение свойства product объекта data.

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

image.png

Данные перенесены из JavaScript на HTML-страницу

Как видите, нам удалось перенести данные из JavaScript-кода на HTML-страницу. А теперь давайте разберёмся в том, что мы только что сделали.

Экземпляр Vue¶

Вот схема кода, с помощью которого создают экземпляр Vue:

In [ ]:
const app = Vue.createApp({ /*...опции...*/ });

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

Подключение экземпляра Vue к элементу веб-страницы¶

Обратите внимание на команду, использованную после создания экземпляра Vue:

In [ ]:
app.mount('#app');

С помощью этого метода мы подключаем экземпляр Vue к элементу нашей страницы. Благодаря этому мы создаём связь между экземпляром Vue и соответствующей частью DOM. Другими словами, мы активируем Vue в элементе <div> с идентификатором app.

Размещение данных в экземпляре Vue¶

В экземпляре Vue имеется место для хранения данных. Эти данные описывают с помощью свойства data объекта с опциями:

In [ ]:
data: ()=>({
    product: "Socks"
})

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

Использование JavaScript-выражений в HTML-коде¶

Если нам нужно, чтобы значение свойства product вывелось бы там, где выводится текст заголовка первого уровня, имя этого свойства можно поместить в двойные фигурные скобки в соответствующем теге:

In [ ]:
<h1>{{ product }}</h1>

Фактически, речь идёт о том, что в двойных фигурных скобках находится JavaScript-выражение, результаты вычисления которого фреймворк подставляет в тег <h1> в качестве текста.

Важный термин: выражение¶

Выражения позволяют использовать значения, хранящиеся в экземпляре Vue, а так же JavaScript-конструкции, применение которых позволяет создавать какие-то новые значения.

Когда Vue видит выражение {{ product }}, он понимает, что мы ссылаемся на данные, связанные с экземпляром Vue, используя ключ product. Фреймворк заменяет имя ключа на соответствующее ему значение. В данном случае это — Socks.

Примеры выражений¶

Как уже было сказано, в двойных фигурных скобках можно использовать различные JavaScript-конструкции. Вот несколько примеров:

In [ ]:
{{ product + '?' }}
{{ firstName + ' ' + lastName }}
{{ message.split('').reverse().join('') }}

Знакомство с реактивностью¶

Причина, по которой Vue сразу же после загрузки страницы выводит в теге <h1> значение, соответствующее свойству product, заключается в том, что Vue — это реактивный фреймворк. Другими словами, данные экземпляра Vue связаны со всеми местами веб-страницы, в которых есть ссылки на эти данные. В результате Vue может не только вывести данные в некоем месте страницы, но и обновить соответствующий HTML-код в том случае, если данные, на которые он ссылается, будут изменены.

Для того чтобы это доказать, давайте откроем консоль инструментов разработчика браузера и изменим значение, записанное в свойство product объекта app. Когда мы это сделаем, например, введя в консоли app.product = 'Coat', изменится и текст, выводимый на странице.

Изменение значения свойства product приводит к изменению текста, выводимого на веб-странице

Видите, как легко это делается?

Практикум¶

Добавьте к уже имеющимся в экземпляре Vue данным ключ description, содержащий текст A pair of warm, fuzzy socks. Затем выведите значение этого ключа в элементе <p>, который должен находиться ниже элемента <h1>.

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

Итоги¶

Поговорим о том, что мы сегодня изучили:

  • Мы узнали о том, как начать разработку Vue-приложения, создав экземпляр Vue, и о том, как выводить данные на веб-страницу.
  • Экземпляр Vue является корнем каждого Vue-приложения.
  • Экземпляр Vue подключается к выбранному при его создании элементу DOM.
  • Данные, хранящиеся в экземпляре Vue, можно выводить на страницу, используя синтаксис Mustache, в котором используются двойные фигурные скобки, {{ }}, содержащие JavaScript-выражения.
  • Vue — это реактивный фреймворк.

Урок 2: привязка атрибутов¶

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

Цель урока¶

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

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

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

In [ ]:
<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:

In [ ]:
const app = Vue.createApp({
        data: ()=>({
            product: "Socks",
            image: "./assets/vmSocks-green.jpg"
        })
    });

    app.mount('#app');

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

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

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

In [ ]:
<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:

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

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

In [ ]:
<img src="./assets/vmSocks-green.jpg" />

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

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

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

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

In [ ]:
image: "./assets/vmSocks-blue.jpg"

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

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

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

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

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

In [ ]:
altText: "A pair of socks"

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

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

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

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

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

In [ ]:
<img :src="image" />

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

Практикум¶

Добавьте на страницу ссылку (элемент <a>) с текстом More products like this. В объекте data создайте свойство link, содержащее ссылку https://www.wildberries.ru/catalog/0/search.aspx?search=носки. Свяжите, используя директиву v-bind, свойство link с атрибутом href элемента <a>.

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

Итоги¶

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

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

Урок 3: условный рендеринг¶

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

Цель урока¶

Нам нужно, чтобы в карточке товара выводилась бы надпись, сообщающая посетителю о том, есть товар на складе, или нет. Если товар на складе есть, должна выводиться надпись In Stock. Если его на складе нет — надпись Out of Stock. Решение о выводе той или иной надписи должно приниматься на основе данных, хранящихся в приложении.

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

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

In [ ]:
<div id="app">
  <div class="product">
    <div class="product-image">
      <img :src="image" />
    </div>

    <div class="product-info">
      <h1>{{ product }}</h1>
    </div>
  </div>
</div>

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

In [ ]:
data: ()=>({
    product: "Socks",
    image: "./assets/vmSocks-green.jpg",
    inStock: true
})

Обратите внимание на то, что в объект data добавлено новое свойство. Это — свойство inStock, хранящее логическое значение true.

Задача¶

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

Соответствующие сообщения планируется оформить в качестве элементов <p>. Это значит, что где-то в index.html будут следующие элементы:

In [ ]:
<p>In Stock</p>
<p>Out of Stock</p>

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

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

В Vue решение этой задачи выглядит просто и понятно.

Как вы уже знаете, данные, указывающие на наличие или отсутствие товара на складе, описаны в main.js, в объекте data:

In [ ]:
inStock: true

Для того чтобы указать системе на то, какой именно элемент

нужно рендерить, мы можем воспользоваться директивами v-if и v-else. Это значит, что в index.html попадёт следующее:

In [ ]:
<p v-if="inStock">In Stock</p>
<p v-else>Out of Stock</p>

Если в inStock содержится истинное значение, выведется первый элемент <p>. В противном случае будет выведен второй элемент. В нашем случае в inStock записано значение true, поэтому выведется In Stock.

image.png

На складе есть запасы товара

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

Директива v-else-if¶

Наш механизм условного рендеринга, основанный на директивах v-if и v-else, можно расширить, добавив в него ещё один уровень логики. Сделать это можно с помощью директивы v-else-if. Для того чтобы это продемонстрировать, давайте немного усложним наш пример.

Предположим, что в объекте data, в main.js, имеются сведения о количестве товара. Они хранятся в свойстве inventory:

In [ ]:
inventory: 100

Анализируя это свойство с помощью JavaScript-выражений, заключённых в кавычки, мы можем сообщать посетителям страницы более точные сведения о товаре:

In [ ]:
<p v-if="inventory > 10">In stock</p>
<p v-else-if="inventory <= 10 && inventory > 0">Almost sold out!</p>
<p v-else>Out of stock</p>

В данной ситуации на страницу выведется первый элемент <p>, так как соответствующее ему выражение оказывается истинным.

Директива v-show¶

Если некий элемент страницы нужно часто скрывать и отображать, это значит, что для реализации этого механизма имеет смысл взглянуть на директиву v-show. Элемент с такой директивой всегда будет присутствовать в DOM, но видимым он будет только в том случае, если условие, переданное директиве, окажется истинным. Фактически, речь идёт о том, что, благодаря использованию этой директивы, к элементу, по условию, будет применяться CSS-свойство display: none.

Этот метод отличается более высокой производительностью, чем управление элементами с использованием v-if и v-else.

Вот как выглядит применение этой директивы:

In [ ]:
<p v-show="inStock">In Stock</p>

Тот вариант решения нашей задачи, в котором использовались директивы v-if и v-else, нас устраивает. Поэтому мы остановимся на нём и не будем ничего менять.

Практикум¶

Добавьте в объект с данными свойство onSale. Оно должно использоваться для управления рендерингом элемента <span>, выводящего текст On Sale и сообщающего посетителям о распродаже.

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

Итоги¶

Сегодня вы узнали об условном рендеринге с использованием механизмов Vue. А именно, речь шла о следующем:

  • Существуют директивы Vue, позволяющие выводить элементы по условию:
  • v-if
  • v-else-if
  • v-else
  • v-show
  • При работе с директивами можно пользоваться JavaScript-выражениями, передаваемыми им в кавычках.
  • Если выражение, передаваемое директиве в кавычках, является истинным, элемент выводится.
  • Директива v-show влияет только на видимость элемента, она не вставляет элементы в DOM и не удаляет элементы из DOM.

Урок 4: рендеринг списков¶

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

Цель урока¶

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

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

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

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

In [ ]:
<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:

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

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

Задача¶

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

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

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

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

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

In [ ]:
<ul>
  <li v-for="detail in details">{{ detail }}</li>
</ul>

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

image.png

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

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

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

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

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

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

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

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

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

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

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

image.png

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

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

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

In [ ]:
<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 рекомендуется назначать каждому выводимому элементу уникальный ключ.

Урок 5: обработка событий¶

Сегодня, в пятом уроке курса по Vue.js для начинающих, речь пойдёт о том, как обрабатывать события.

Цель урока¶

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

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

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

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

In [ ]:
<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:

In [ ]:
const app = Vue.createApp({
        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"
              }
            ]
        })
    });

    app.mount('#app');

Задача¶

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

Решение¶

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

In [ ]:
cart: 0

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

In [ ]:
<div class="cart">
  <p>Cart({{ cart }})</p>
</div>

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

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

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

image.png

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

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

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

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

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

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

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

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

In [ ]:
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 к такому виду:

In [ ]:
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:

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

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

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

In [ ]:
updateProduct(variantImage) {
  this.image = variantImage
}

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

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

In [ ]:
<p @mouseover="updateProduct(variant.variantImage)">

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

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

Синтаксис ES6¶

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

In [ ]:
updateProduct(variantImage) {
  this.image = variantImage
}

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

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

Практикум¶

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

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

Итоги¶

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

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

Урок 6: привязка классов и стилей¶

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

Цель урока¶

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

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

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

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

In [ ]:
<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:

In [ ]:
const app = createApp({
    data() {
        return {
            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;
        }
    }
});

app.mount('#app');

Задача¶

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

Решение¶

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

In [ ]:
<div
  class="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.

image.png

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

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

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

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

image.png

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

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

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

Задача¶

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

In [ ]:
inStock: true,

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

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

In [ ]:
<button
  v-on:click="addToCart"
  :disabled="!inStock"
>
  Add to cart
</button>

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

image.png

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

Решение¶

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

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

image.png

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

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

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

Взгляните на эту строчку: :class="{ disabledButton: !inStock }"

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

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

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

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

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

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

Практикум¶

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

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

Итоги¶

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

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

Урок 7: вычисляемые свойства¶

Сегодня, в седьмом уроке курса по Vue, мы поговорим о вычисляемых свойствах. Эти свойства экземпляра Vue не хранят значения, а вычисляют их.

Цель урока¶

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

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

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

In [ ]:
<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>
      <div
        class="color-box"
        v-for="variant in variants"
        :key="variant.variantId"
        :style="{ backgroundColor:variant.variantColor }"
        @mouseover="updateProduct(variant.variantImage)"
      ></div>

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

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

Вот код main.js:

In [ ]:
const app = createApp({
    data() {
        return {
            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;
        }
    }
});

app.mount('#app');

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

Задача¶

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

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

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

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

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

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

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

In [ ]:
<h1>{{ product }}</h1>

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

In [ ]:
<h1>{{ title }}</h1>

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

image.png

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

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

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

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

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

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

In [ ]:
updateProduct(variantImage) {
  this.image = variantImage;
}

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

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

In [ ]:
selectedVariant: 0,

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

In [ ]:
<div
  class="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:

In [ ]:
@mouseover="updateProduct(index)"

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

In [ ]:
updateProduct(index) {
  this.selectedVariant = index;
  console.log(index);
}

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

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

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

image.png

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

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

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

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

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

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

In [ ]:
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:

In [ ]:
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 формирует строку, сообщающую о том, проводится ли сейчас распродажа или нет. Выведите эту строку в карточке товара.

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

Итоги¶

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

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

Урок 8: компоненты¶

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

Цель урока¶

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

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

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

In [ ]:
<div id="app">
  <div class="product">
    <div class="product-image">
      <img :src="image" />
    </div>

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

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

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

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

Вот код main.js:

In [ ]:
const app = Vue.createApp({
  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;
    }
  }
});

app.mount('#app');

Задача¶

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

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

Начнём с того, что возьмём существующий код и перенесём его в новый компонент.

Вот как в файле main.js регистрируется компонент:

In [ ]:
app.component('product', {})

Первый аргумент — это выбранное нами имя компонента. Второй — это объект с опциями, похожий на тот, который мы использовали при создании экземпляра Vue на прошлых занятиях.

В экземпляре Vue мы использовали идентификатор #app для организации его привязки к элементу DOM. В случае с компонентом используется свойство template, которое определяет HTML-код компонента.

Опишем шаблон компонента в объекте с опциями:

In [ ]:
app.component('product', {
  template: `
    <div class="product">
… // Здесь будет весь HTML-код, который раньше был в элементе с классом product
    </div>
  `
})

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

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

In [ ]:
app.component('product', {
  template: `
  <div class="product">
…
  </div>
  `,
  data() {
    return {
      // тут будут данные
    }
  },
    methods: {
      // тут будут методы
    },
    computed: {
      // тут будут вычисляемые свойства
    }
})

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

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

In [ ]:
const app = Vue.createApp({});

app.component('product', { ... код продукта переехал сюда ... });

app.mount('#app');

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

In [ ]:
<div id="app">
  <product></product>
</div>

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

image.png

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

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

image.png

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

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

In [ ]:
<div id="app">
  <product></product>
  <product></product>
  <product></product>
</div>

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

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

In [ ]:
<div id="app">
  <product></product>
</div>

Задача¶

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

Пусть в корневом экземпляре Vue имеется описание неких данных. Эти данные указывают на то, является ли пользователь обладателем премиум-аккаунта. Код описания экземпляра Vue при этом может выглядеть так:

In [ ]:
const app = Vue.createApp({
  data() {
    return {
      premium: true
    };
  }
});

app.mount('#app');

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

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

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

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

Во Vue, для передачи данных от родительских сущностей дочерним, применяется свойство объекта с опциями props, описываемое у компонентов. Это объект с описанием входных параметров компонента, значения которых должны быть заданы на основе данных, получаемых от родительской сущности.

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

In [ ]:
app.component('product', {
  props: {
    premium: {
      type: Boolean,
      required: true
    }
  },
  // Тут будут описания данных, методов, вычисляемых свойств
})

Обратите внимание на то, что тут используются встроенные возможности Vue по проверке параметров, передаваемых компоненту. А именно, мы указываем то, что типом входного параметра premium является Boolean, и то, что этот параметр является обязательным, устанавливая required в true.

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

In [ ]:
<p>User is premium: {{ premium }}</p>

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

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

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

In [ ]:
<div id="app">
  <product :premium="premium"></product>
</div>

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

image.png

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

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

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

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

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

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

image.png

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

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

In [ ]:
shipping() {
  if (this.premium) {
    return "Free";
  } else {
    return 2.99
  }
}

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

Уберём из шаблона компонента код вывода значения параметра premium. Теперь элемент <p>Shipping: {{ shipping }}</p>, который присутствовал в коде, с которого мы сегодня начали работу, сможет вывести сведения о стоимости доставки.

image.png

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

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

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

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

Практикум¶

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

In [ ]:
<ul>
  <li v-for="detail in details">{{ detail }}</li>
</ul>

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

Итоги¶

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

  • Компоненты — это блоки кода, представленные в виде пользовательских элементов.
  • Компоненты упрощают управление приложением благодаря тому, что позволяют разделить его на части, подходящие для многократного использования. Они содержат в себе описания визуальной составляющей и функционала соответствующей части приложения.
  • Данные компонента представлены методом data() объекта с опциями.
  • Для передачи данных от родительских сущностей дочерним сущностям используются входные параметры (props).
  • Мы можем описать требования к входным параметрам, которые принимает компонент.

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

  • Данные родительского компонента можно динамически привязать к пользовательским атрибутам.
  • Инструменты разработчика Vue дают ценные сведения о компонентах.

PS¶

В скором времени в этой статье появятся еще 2 урока:

  • https://www.vuemastery.com/courses/intro-to-vue-3/communicating-events-vue3
  • https://www.vuemastery.com/courses/intro-to-vue-3/forms-and-v-model-vue3

Они пока не переведены, но их можно пройти в оригинале, включив автопереводчик в браузере.

Контрольные задания для самостоятельного решения¶

Задание 1: Экземпляр Vue и вывод данных¶

Цель: Научиться создавать экземпляр Vue и выводить данные на страницу.

Задача:

  1. Создайте HTML-файл с элементом <div id="app">.
  2. Подключите Vue.js через CDN.
  3. Создайте экземпляр Vue, который будет содержать данные: title: "Мой первый Vue-проект" и description: "Это описание моего проекта".
  4. Выведите данные title и description на страницу с использованием синтаксиса Mustache ({{ }}).

Проверка:

  • Убедитесь, что на странице отображаются значения title и description.

Задание 2: Привязка атрибутов¶

Цель: Научиться привязывать данные к атрибутам HTML-элементов.

Задача:

  1. Создайте экземпляр Vue с данными: imageUrl: "https://example.com/image.jpg" и altText: "Пример изображения".
  2. Используйте директиву v-bind (или её сокращённую форму :) для привязки imageUrl к атрибуту src элемента <img> и altText к атрибуту alt.
  3. Добавьте элемент <a> с привязкой атрибута href к данным link: "https://example.com".

Проверка:

  • Убедитесь, что изображение отображается корректно, а ссылка ведёт на указанный адрес.

Задание 3: Условный рендеринг¶

Цель: Научиться управлять отображением элементов на странице с помощью условного рендеринга.

Задача:

  1. Создайте экземпляр Vue с данными: inStock: true и onSale: false.
  2. Используйте директиву v-if для отображения текста "В наличии", если inStock равно true, и "Нет в наличии", если false.
  3. Используйте директиву v-show для отображения текста "Распродажа", если onSale равно true.

Проверка:

  • Убедитесь, что текст "В наличии" или "Нет в наличии" отображается в зависимости от значения inStock.
  • Убедитесь, что текст "Распродажа" появляется или исчезает при изменении значения onSale.

Задание 4: Рендеринг списков¶

Цель: Научиться выводить списки данных на страницу с помощью директивы v-for.

Задача:

  1. Создайте экземпляр Vue с данными: sizes: ['S', 'M', 'L', 'XL'].
  2. Используйте директиву v-for для отображения списка размеров в виде элементов <li> внутри <ul>.
  3. Добавьте свойство key для каждого элемента списка, используя индекс элемента.

Проверка:

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

Задание 5: Обработка событий¶

Цель: Научиться обрабатывать события с помощью директивы v-on.

Задача:

  1. Создайте экземпляр Vue с данными: cart: 0.
  2. Добавьте кнопку с текстом "Добавить в корзину".
  3. Используйте директиву v-on:click (или её сокращённую форму @click) для увеличения значения cart на 1 при каждом нажатии на кнопку.
  4. Отобразите текущее значение cart на странице.

Проверка:

  • Убедитесь, что при нажатии на кнопку значение cart увеличивается, и это отображается на странице.

Дополнительное задание (по желанию): Компоненты¶

Цель: Научиться создавать и использовать компоненты.

Задача:

  1. Создайте компонент product-details, который принимает входной параметр details (массив строк) и отображает их в виде списка <ul>.
  2. Используйте этот компонент в основном экземпляре Vue, передавая ему данные: details: ['80% cotton', '20% polyester', 'Gender-neutral'].

Проверка:

  • Убедитесь, что компонент корректно отображает переданные данные в виде списка.