После всех ужасов, которые я испытал, пытаясь учить и работать на Реакте, я решил попробовать 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 реактивностью на примере полей и кнопок, а значит будем писать больше методов, добавим в проект поиск, фильтры и корзину, куда можно складывать товары.