Проект на Vue.js. Часть 1. Установка, основные элементы и проход по списку

После всех ужасов, которые я испытал, пытаясь учить и работать на Реакте, я решил попробовать Vue.js и не прогадал. Это оказался такой приятный фреймворк, что я решил поделиться небольшим туториалом, как с ним работать.

В этой статье будем создавать базовую страничку с каталогом товаров. Я в своём стиле буду делать музыкальный магазин.

Создание пустого проекта с vue.js

Начнём с создания проекта, конечно же. Для этого нам нужен Node.js. Если он установлен то эти команды вернут номер версий:

node -v
npm -v

Как только вы поставили Node (можно скачать с их сайта), то заходим в папку, где будет лежать проект и создаём его:

npm create vite@latest

При создании в командной строке будут появляться вопросы: сначала вводим имя проекта, потом выбираем фреймворк Vue и наконец JavaScript (можно конечно выбрать другие варианты, но я работаю именно с такой настройкой).

Также меня спросили Install with npm and start now? Если нажать Yes, то проект будет запущен. Если нажать No, то самостоятельно нужно будет зайти в папку с созданным проектом и выполнить команды:

npm install
npm run dev

Зачем нужен npm install? Он создаёт файл package-lock.json, папку node_modules и скачивает все зависимости. Если этого не сделать а сразу попытаться запустить проект, то проект просто не запустится. Я вот получаю сообщение

«vite» не является внутренней или внешней

командой, исполняемой программой или пакетным файлом.

После всех этих операций проект точно запустится – по адресу localhost:5173 получил стандартное приложение Hello World с счётчиком. Чтобы остановить сервер, нажимаем Ctrl+C.

Структура проекта

Теперь посмотрим, где что лежит в этом пустом приложении и очистим лишнее.

index.html это корневой HTML-файл. Единственный, который реально загружает браузер.

Внутри него самое главное:

<div id="app"></div>
/src/main.js

Тут создаётся пустой контейнер app – именно в него Vue вставляет приложение. Но мы не пишем сюда HTML, вместо этого будем работать с Vue-компонентами

src/main.js – точка входа. Первый файл, который запускается при старте приложения.

Он обычно выглядит так:

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

Здесь мы монтируем созданное приложение из App.vue в элемент app на странице index.html. Если там вы меняете название этого элемента, то тут его тоже обязательно надо изменить.

src/App.vue – главный компонент, который мы уже упомянули в прошлом файле. Он загружается первым иможет включать разметку (template), логику (script) и стили (style)

src/assets/ – папка для стилей, картинок, шрифтов и любых статических файлов, которые импортируются в JS/Vue

package.json хранит список зависимостей (Vue, Vite и др.), версии пакетов, команды для запуска (scripts) и метаданные проекта

Здесь мы видим следующее:

"scripts": {
  "dev": "vite",
  "build": "vite build",
  "preview": "vite preview"
}

Именно поэтому запускаем проект так:

npm run dev

node_modules/ – папка, которая появится после npm install с Vue, Vite, плагинами и прочими зависимостями. Её можно добавить в .gitignore, если вы собираетесь использовать гит.

vite.config.js – настройки сборщика Vite (он нужен для запуска сервера и сборки проекта)

public/ – это папка для файлов, которые должны попасть в итоговую сборку как есть, без обработки Vite.

В чём отличие assets от public? src/assets/ используется для файлов, которые импортируются в код и обрабатываются Vite (оптимизация, хэширование). А в public/ файлы остаются неизменными и доступны напрямую по URL.

components – папка для компонентов Vue, и пока она нам будет не нужна, так что предлагаю начать очистку именно с удаления HelloWorld.vue.

Далее очищаем стили – они нужны лишь для демо-приложения.

И наконец заменяем содержимое App.vue на

<template>
  <div>Hello Vue</div>
</template>

<script>
export default {
  name: "App"
}
</script>

<style>
</style>

Вы ещё можете встретить вариант script setup, но я использую классический вариант, хоть и пишу на Vue 3. Он, кажется, ничуть не хуже.

Немного о Vue: преимущества

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

Он также достаточно легковесный и быстрый в связке с Vite (что, кстати, значит «быстрый» с французского).

Но самое главное, что он реактивный – то есть если мы меняем данные, то они автоматически обновятся на странице (не надо вручную в js прописывать скрипт, который подгружает новые значения в поля и объекты). И чтобы это попробовать, предлагаю перейти к наполнению страницы данными.

Синтаксис Vue

Для начала стоит разобраться, что вообще есть во Vue, и как этим всем пользоваться.

Шаблонные выражения: {{ }}

Это самый базовый синтаксис Vue. Внутри двойных фигурных скобок (усы — с английского moustache) можно выводить любые данные или даже выражения, которые будут вычисляться:

<p>{{ message }}</p>
<p>{{ price * amount }}</p>

Если в компоненте есть:

data() {
  return { message: "Hello Vue!" }
}

Но сюда нельзя писать сложный код (циклы, объявления переменных), мы работаем только с данными.

Директивы Vue

Это специальные атрибуты, которые начинаются с v-.

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

Самые важные:

v-bind (или просто :). Подставляет данные в атрибут:

<img :src="imageUrl">

v-on (или @). Обработка событий:

<button @click="addToCart">Добавить в корзину</button>

v-if, v-else-if, v-else. Условный рендеринг:

<p v-if="inStock">Есть на складе</p>
<p v-else>Нет на складе</p>

v-show. Просто скрывает элемент, но не удаляет из DOM:

<p v-show="visible">Меня видно?

v-for. Рендер списка:

<li v-for="product in products">
  {{ product.name }}
</li>

Референции (реактивные переменные)

Все данные компонента должны возвращаться из функции data():

data() {
  return {
    count: 0,
    username: 'Egor'
  }
}

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

Методы

Любые функции логики пишутся в блоке methods:

methods: {
  increment() {
    this.count++
  }
}

И вызываются из шаблона по @ (v-on):

<button @click="increment">+</button>

Вычисляемые свойства (computed)

Когда значение зависит от других данных — удобно использовать computed:

computed: {
  fullName() {
    return this.first + ' ' + this.last
  }
}

Какая разница между computed и methods? computed кэшируется, а method выполняется каждый раз заново.

То есть если зависимые переменные не изменились, computed НЕ пересчитывается и Vue берёт ранее сохранённый результат.

Например

computed: {
  cartTotal() {
    console.log('recomputed')
    return this.items.reduce((sum, item) => sum + item.price, 0)
  }
}

И в шаблоне:

<div>{{ cartTotal }}</div>
<div>{{ cartTotal }}</div>
<div>{{ cartTotal }}</div>

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

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

methods: {
  cartTotalMethod() {
    console.log('method called')
    return this.items.reduce((sum, item) => sum + item.price, 0)
  }
}

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

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

Vue помогает динамически менять классы:

<div :class="{ active: isActive }"></div>

Или стили:

<div :style="{ color: textColor }"></div>

Рендеринг списков

Итак, начнём разработку магазина. Для начала я создал фиктивный список данных в формате json. Для начала я поместил его по адресу ‎src/data/products.js. Здесь я использую не JSON-файл, а обычный .js с экспортом – так проще импортировать данные напрямую без fetch, пока проект маленький.

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

export const products = [
  {
    id: 20,
    artist: "ABBA",
    name: "Voyage",
    catNo: "ABBA-VOYAGE",
    format: "vinyl",
    note: "standard black vinyl",
    img: "/src/assets/img/ABBA_-_Voyage.png",
    price: 28
  }
]

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

Для начала опишем скрипт.

<script>
import { products } from './data/products.js'

export default {
  data() {
    return {
      items: products
    }
  },
  methods: {
    currency(value) {
      return `$${Number.parseFloat(value).toFixed(2)}`
    }
  }
}
</script>

Здесь всё довольно просто: мы импортируем скрипт, который передаёт нам данные, а потом объявляем их под именем items. В методы я добавил пока лишь один метод для форматирования цен. Он добавляет значок доллара и оставляет две цифры после точки с помощью toFixed(2).

Чтобы пробежаться в цикле по всем элементам из items, нужно создать элемент div с директивой v-for и значением item in items. Тогда внутри этого тега мы можем обращаться ко всем полям наших объектов.

Например, чтобы просто вывести текстом названия артиста, мы напишем {{ item.artist }}

Чтобы использовать адрес изображения из item.img, в теге img связываем src с этим значением и делаем :src=”item.img”. Тут фигурные скобки не нужны, и любое использование значений переменных в атрибутах тега требует двоеточия. Мы могли использовать полный синтаксис с v-bind, но это совсем не обязательно:

<img v-bind:src="item.img">
<img :src="item.img">

Такой у меня получился полный код:

<template>
  <div class="page">
    <header class="header">
      <div class="logo-wrap">
        <img
          class="logo-img"
          src="/src/assets/logo.jpg"
          alt="Winylka logo"
        >
        <div class="header-text">
          <h1 class="site-title">Winylka Records</h1>
          <p class="site-subtitle">Selected vinyl records</p>
        </div>
      </div>
    </header>

    <h2 class="section-title">Catalogue</h2>

    <div class="grid">
      <article v-for="item in items" :key="item.id" class="product-card">
        <img class="product-image" :src="item.img" :title="item.name" :alt="item.name">

        <div class="product-body">
          <h3 class="product-title">
            {{ item.artist }} – {{ item.name }}
          </h3>

          <p class="product-note">{{ item.note }}</p>
        </div>

        <div class="product-bottom">
          <div class="product-price">{{ currency(item.price) }}</div>

          <div class="product-tags">
            <span v-if="item.price < sale" class="tag tag-sale">SALE!</span>
            <span v-else-if="item.price < cheap" class="tag tag-good">GOOD OFFER!</span>

            <span class="tag tag-format">{{ item.format }}</span>
          </div>
        </div>
      </article>
    </div>
  </div>
</template>

Здесь я добавил шапку на страничку и уже объявил стили, но мы с ними поработаем позже. Пока их можно разместить в теге <style> на этой же странице.

Я также использовал условную логику для вывода меток о скидке или хорошем предложении, позволяя вывести лишь одну из меток (поэтому использовал v-if и v-else-if).

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


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

В следующей статье ещё поработаем c реактивностью на примере полей и кнопок, а значит будем писать больше методов, добавим в проект поиск, фильтры и корзину, куда можно складывать товары.

Оставьте комментарий