Олег Марков
Основы Vue - vue-basics для уверенного старта
Введение
Vue часто выбирают как первый фреймворк для фронтенда. Он позволяет описывать интерфейс декларативно, то есть вы не думаете о том, как обновить DOM вручную, а просто описываете состояние, и Vue сам синхронизирует его с разметкой.
Здесь вы увидите базовый путь от пустой HTML‑страницы до небольшого приложения на Vue. Я покажу, как устроена реактивность, как работают шаблоны, директивы и события, и как из всего этого собрать простые, но уже полезные компоненты.
Что такое Vue и откуда он запускается
Кратко о концепции
Vue — это фреймворк для создания пользовательских интерфейсов. Главное, что важно понимать на старте:
- Вы описываете данные в обычных JavaScript‑объектах.
- Вы пишете шаблоны, где связываете данные и верстку.
- Vue автоматически обновляет HTML, когда меняются данные.
То есть вы перестаете писать код вида:
// Псевдокод без Vue
const titleElement = document.getElementById("title")
titleElement.textContent = state.title
и вместо этого описываете:
<h1>{{ title }}</h1>
А значение title берется из объекта данных, который вы передали в приложение Vue.
Установка и запуск первого приложения
Вариант 1 — через CDN на простой странице
Этот способ подходит, если вы хотите просто попробовать Vue без сборщиков, npm и конфигураций.
Создайте файл index.html со следующим содержимым:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<title>Первое приложение на Vue</title>
<!-- Подключаем Vue через CDN -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<!-- Корневой элемент приложения -->
<div id="app">
<!-- Интерполяция данных из Vue -->
<h1>{{ message }}</h1>
<!-- Обработка клика по кнопке -->
<button @click="changeMessage">
Изменить сообщение
</button>
</div>
<script>
// Создаем приложение Vue
const app = Vue.createApp({
// Здесь мы определяем реактивные данные
data() {
return {
message: "Привет из Vue"
}
},
// Здесь описываем методы
methods: {
changeMessage() {
// Меняем данные - Vue сам обновит интерфейс
this.message = "Сообщение изменено"
}
}
})
// Монтируем приложение к элементу с id="app"
app.mount("#app")
</script>
</body>
</html>
Смотрите, я показал вам самый простой пример:
- объект из
dataстановится источником данных; - шаблон внутри
<div id="app">использует эти данные; - при клике вызывается метод
changeMessage, который меняет состояние, а Vue обновляет DOM.
Вариант 2 — через Vite и npm
Если вы планируете реальный проект, удобнее использовать Vite или Vue CLI. Сейчас экосистема Vue движется в сторону Vite, поэтому покажу этот путь.
Предположим, у вас уже установлены Node.js и npm.
В терминале выполните:
npm create vite@latest my-vue-app -- --template vue// Здесь мы создаем новый проект my-vue-app с шаблоном Vue
Зайдите в папку проекта:
cd my-vue-appУстановите зависимости:
npm installЗапустите дев‑сервер:
npm run dev// После этого вы получите локальный адрес (обычно http://localhost:5173)
Vite создаст структуру проекта, где уже есть main.js и корневой компонент App.vue. Чуть позже мы разберем, что в них происходит, когда будем говорить о компонентах.
Основы реактивности и объекта данных
Объект data в приложении (Options API)
В базовых примерах Vue 3 чаще всего используют подход Options API. Смотрите, как это выглядит:
const app = Vue.createApp({
// data - это функция, которая возвращает объект с данными
data() {
return {
counter: 0, // Число для счетчика
username: "Гость", // Имя пользователя
isVisible: true // Флаг отображения элемента
}
}
})
Каждое поле из возвращаемого объекта становится реактивным. Это значит, что:
- вы можете использовать его в шаблоне;
- при изменении значения Vue сам обновит интерфейс.
Теперь давайте разберемся на примере с шаблоном:
<div id="app">
<!-- Выводим значение переменной counter -->
<p>Счетчик - {{ counter }}</p>
<!-- При клике вызываем метод увеличения -->
<button @click="counter++">
Увеличить
</button>
<!-- Условный рендеринг на основе isVisible -->
<p v-if="isVisible">
Этот текст можно скрыть
</p>
<button @click="isVisible = !isVisible">
Показать или скрыть текст
</button>
</div>
const app = Vue.createApp({
data() {
return {
counter: 0, // Начальное значение счетчика
isVisible: true // Элемент изначально виден
}
}
})
app.mount("#app")
Как только вы изменяете counter или isVisible, DOM перестраивается автоматически. Вам не нужно искать элементы через document.querySelector.
Шаблоны и интерполяция
Интерполяция выражений
Шаблон в Vue позволяет вставлять значения с помощью двойных фигурных скобок:
<p>Привет, {{ username }}!</p>
<p>Через год вам будет {{ age + 1 }} лет.</p>
Внутри {{ }} можно использовать простые JS‑выражения:
- арифметику
age + 1; - тернарный оператор
isAdmin ? 'Админ' : 'Пользователь'; - вызов методов, которые вы объявили в
methods.
Например:
<p>{{ greetingMessage() }}</p>
const app = Vue.createApp({
data() {
return {
username: "Алиса" // Имя пользователя
}
},
methods: {
greetingMessage() {
// Формируем строку на основе данных
return "Добро пожаловать, " + this.username
}
}
})
Атрибуты и директива v-bind
Иногда вам нужно подставить значение не в текст, а в атрибут: например, сделать src картинки динамическим. Для этого используют v-bind.
<img v-bind:src="imageUrl" v-bind:alt="imageDescription" />
Сокращенная запись:
<img :src="imageUrl" :alt="imageDescription" />
И соответствующий код:
const app = Vue.createApp({
data() {
return {
imageUrl: "https://example.com/cat.png", // Ссылка на картинку
imageDescription: "Изображение кота" // Описание для alt
}
}
})
Вы можете биндить не только строки, но и булевы значения и объекты. Например, динамический класс:
<p :class="{ active: isActive, error: hasError }">
Текст с динамическими классами
</p>
const app = Vue.createApp({
data() {
return {
isActive: true, // Добавит класс active
hasError: false // Класс error не будет добавлен
}
}
})
Здесь я использую объект: ключи — названия классов, значения — булевы флаги.
Основные директивы Vue
Директивы — это специальные атрибуты в шаблоне, которые расширяют поведение HTML‑элементов.
v-if и v-show — условный рендеринг
v-if
v-if добавляет или полностью удаляет элемент из DOM в зависимости от условия.
<p v-if="isLoggedIn">
Вы авторизованы
</p>
<p v-else>
Пожалуйста, войдите в систему
</p>
const app = Vue.createApp({
data() {
return {
isLoggedIn: false // Изначально пользователь не авторизован
}
}
})
Когда isLoggedIn становится true, первый <p> появляется, а второй исчезает.
v-show
v-show управляет только CSS‑свойством display. Элемент всегда есть в DOM, но может быть скрыт.
<p v-show="isVisible">
Этот текст скрывается через display none
</p>
const app = Vue.createApp({
data() {
return {
isVisible: true // Элемент виден
}
}
})
Разница:
v-if— дороже при частом переключении, но не рендерит элемент, когда он не нужен;v-show— быстро переключает видимость, но всегда держит элемент в DOM.
v-for — циклы и списки
Смотрите, я покажу вам, как отрисовать список элементов:
<ul>
<!-- Перебираем массив items -->
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
const app = Vue.createApp({
data() {
return {
items: [
{ id: 1, name: "Элемент 1" }, // Первый элемент списка
{ id: 2, name: "Элемент 2" }, // Второй элемент списка
{ id: 3, name: "Элемент 3" } // Третий элемент списка
]
}
}
})
Пара важных деталей:
v-for="item in items"— перебор массива;:key="item.id"— уникальный ключ для отслеживания элементов при изменениях (это важно для производительности и корректной работы).
Можно получить и индекс:
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.name }}
</li>
Обработка событий
v-on и сокращение @
Для подписки на события используют директиву v-on. Чаще вы увидите сокращение @.
<button v-on:click="increment">
Увеличить
</button>
<button @click="decrement">
Уменьшить
</button>
const app = Vue.createApp({
data() {
return {
counter: 0 // Счетчик на экране
}
},
methods: {
increment() {
// Увеличиваем значение
this.counter++
},
decrement() {
// Уменьшаем значение
this.counter--
}
}
})
Теперь вы увидите, как при клике на каждую кнопку меняется значение в интерфейсе.
Передача параметров в обработчик
Если нужно передать аргумент, делаем так:
<button @click="add(5)">
Добавить 5
</button>
const app = Vue.createApp({
data() {
return {
counter: 0 // Исходное значение счетчика
}
},
methods: {
add(amount) {
// Увеличиваем на переданное значение
this.counter += amount
}
}
})
Если нужен объект события, его можно передать вручную:
<button @click="handleClick($event)">
Кликни меня
</button>
const app = Vue.createApp({
methods: {
handleClick(event) {
// Здесь мы можем работать с объектом события
console.log("Тег события:", event.target.tagName)
}
}
})
Модификаторы событий
Vue предлагает удобные модификаторы, чтобы не писать однотипный код.
Популярные:
.prevent— вызываетevent.preventDefault();.stop— вызываетevent.stopPropagation();.enter,.escи другие клавишные модификаторы.
Пример отправки формы без перезагрузки:
<form @submit.prevent="submitForm">
<!-- Поля формы -->
<button type="submit">Отправить</button>
</form>
const app = Vue.createApp({
methods: {
submitForm() {
// Здесь мы обрабатываем данные формы без перезагрузки страницы
console.log("Форма отправлена")
}
}
})
Двустороннее связывание и v-model
Связь поля ввода и состояния
Двустороннее связывание v-model используется чаще всего с <input>, <textarea> и <select>.
<div id="app">
<!-- Поле ввода связано с переменной username -->
<input v-model="username" placeholder="Введите имя" />
<!-- Текст автоматически обновляется при изменении username -->
<p>Вы ввели - {{ username }}</p>
</div>
const app = Vue.createApp({
data() {
return {
username: "" // Строка для хранения имени пользователя
}
}
})
app.mount("#app")
Когда вы что-то вводите в поле, Vue автоматически меняет username. Когда вы меняете username из кода, поле ввода также обновляется.
v-model и чекбоксы, радио, select
Давайте посмотрим, как это работает с другими типами:
<div id="app">
<!-- Чекбокс с булевым значением -->
<label>
<input type="checkbox" v-model="isSubscribed" />
Подписаться на новости
</label>
<!-- Группа радио-кнопок -->
<div>
<label>
<input type="radio" value="light" v-model="theme" />
Светлая тема
</label>
<label>
<input type="radio" value="dark" v-model="theme" />
Темная тема
</label>
</div>
<!-- Выпадающий список -->
<select v-model="selectedCountry">
<option disabled value="">Выберите страну</option>
<option value="ru">Россия</option>
<option value="us">США</option>
<option value="de">Германия</option>
</select>
<pre>
isSubscribed - {{ isSubscribed }}
theme - {{ theme }}
selectedCountry - {{ selectedCountry }}
</pre>
</div>
const app = Vue.createApp({
data() {
return {
isSubscribed: false, // Для чекбокса
theme: "light", // Для радио-кнопок
selectedCountry: "" // Для select
}
}
})
app.mount("#app")
Обратите внимание, как данные меняются сразу в нескольких местах интерфейса без ручного управления DOM.
Вычисляемые свойства и наблюдатели
computed — вычисляемые значения
Иногда вам нужно производное значение, которое зависит от нескольких полей. Например, полное имя из имени и фамилии.
<div id="app">
<input v-model="firstName" placeholder="Имя" />
<input v-model="lastName" placeholder="Фамилия" />
<p>Полное имя - {{ fullName }}</p>
</div>
const app = Vue.createApp({
data() {
return {
firstName: "", // Имя
lastName: "" // Фамилия
}
},
computed: {
fullName() {
// Возвращаем строку, зависящую от двух полей
return this.firstName + " " + this.lastName
}
}
})
app.mount("#app")
Особенности computed:
- к ним обращаются как к обычным свойствам (
{{ fullName }}); - они кэшируются и пересчитываются только при изменении зависимостей (
firstNameилиlastName).
watch — реакция на изменение
watch позволяет отслеживать изменение конкретного поля и выполнять побочные действия, например, вызывать API или логировать.
const app = Vue.createApp({
data() {
return {
searchQuery: "" // Строка поиска
}
},
watch: {
searchQuery(newValue, oldValue) {
// Реагируем на изменение строки поиска
console.log("Поисковый запрос изменился")
console.log("Старое значение:", oldValue)
console.log("Новое значение:", newValue)
// Здесь можно, например, вызывать API с задержкой
}
}
})
watch удобно использовать, когда вам нужно делать асинхронные операции при изменении состояния.
Введение в компоненты
Зачем нужны компоненты
Когда приложение растет, неудобно держать всю логику в одном Vue‑экземпляре. Компоненты позволяют:
- разбивать интерфейс на небольшие независимые части;
- переиспользовать эти части;
- изолировать данные и логику.
Регистрация простого компонента (через CDN)
Смотрите, я покажу вам, как создать два компонента: один показывает заголовок, второй — счетчик.
<div id="app">
<!-- Используем компонент заголовка -->
<app-header></app-header>
<!-- Используем компонент счетчика -->
<counter-button></counter-button>
<counter-button></counter-button>
</div>
<script>
const app = Vue.createApp({})
// Регистрируем глобальный компонент app-header
app.component("app-header", {
template: `
<header>
<h1>Мое приложение на Vue</h1>
</header>
`
// Здесь можно добавить данные и методы, если нужно
})
// Регистрируем глобальный компонент counter-button
app.component("counter-button", {
// data в компоненте - это функция (чтобы каждый экземпляр имел свои данные)
data() {
return {
count: 0 // Локальный счетчик компонента
}
},
methods: {
increment() {
// Увеличиваем локальный счетчик
this.count++
}
},
template: `
<button @click="increment">
Нажато {{ count }} раз
</button>
`
})
app.mount("#app")
</script>
Каждый <counter-button> хранит свой собственный count. Это важно: данные компонента не разделяются между экземплярами.
Взаимодействие компонентов - props и события
Передача данных от родителя к ребенку - props
Представьте, что у вас есть компонент кнопки, которому вы хотите передать текст.
<div id="app">
<!-- Передаем текст через атрибут label -->
<custom-button label="Сохранить"></custom-button>
<custom-button label="Отменить"></custom-button>
</div>
<script>
const app = Vue.createApp({})
app.component("custom-button", {
// Описываем принимаемые свойства
props: {
label: {
type: String, // Ожидаем строку
required: true // Обязательно передавать
}
},
template: `
<button>
{{ label }}
</button>
`
})
app.mount("#app")
</script>
Вы можете использовать props в шаблоне и в методах компонента. Главное правило: props — входные данные, их не изменяют внутри дочернего компонента, а работают с ними как с "read only".
Подъем событий вверх
Чтобы ребенок "сообщил" что-то родителю, используют пользовательские события.
Давайте разберемся на примере:
<div id="app">
<!-- Подписка на кастомное событие increment -->
<counter-button @increment="total++"></counter-button>
<counter-button @increment="total++"></counter-button>
<p>Всего нажатий - {{ total }}</p>
</div>
const app = Vue.createApp({
data() {
return {
total: 0 // Общий счетчик всех кнопок
}
}
})
app.component("counter-button", {
data() {
return {
count: 0 // Локальный счетчик кнопки
}
},
methods: {
handleClick() {
// Увеличиваем локальный счетчик
this.count++
// Генерируем событие increment для родителя
this.$emit("increment")
}
},
template: `
<button @click="handleClick">
Нажато {{ count }} раз
</button>
`
})
app.mount("#app")
Обратите внимание:
- в дочернем компоненте мы вызываем
this.$emit("increment"); - родитель подписывается через
@increment="total++".
Так вы отделяете внутреннюю реализацию компонента от внешнего поведения.
Кратко о Single File Components (SFC)
Когда вы запускаете проект через Vite, вы работаете с файлами .vue. Это однофайловые компоненты (Single File Components). В них шаблон, логика и стили собраны вместе.
Пример App.vue:
<template>
<!-- Разметка компонента -->
<div class="app">
<h1>{{ title }}</h1>
<counter-button></counter-button>
</div>
</template>
<script>
export default {
name: "App", // Имя компонента
data() {
return {
title: "Приложение на Vue и Vite" // Заголовок
}
}
}
</script>
<style>
/* Стили компонента */
.app {
font-family: sans-serif;
}
</style>
Компонент CounterButton.vue может выглядеть так:
<template>
<button @click="increment">
Нажато {{ count }} раз
</button>
</template>
<script>
export default {
name: "CounterButton",
data() {
return {
count: 0 // Локальное состояние кнопки
}
},
methods: {
increment() {
// Увеличиваем локальное состояние
this.count++
}
}
}
</script>
И подключение в App.vue:
<template>
<div>
<h1>{{ title }}</h1>
<CounterButton />
<CounterButton />
</div>
</template>
<script>
import CounterButton from "./components/CounterButton.vue" // Импорт компонента
export default {
name: "App",
components: {
CounterButton // Регистрируем импортированный компонент
},
data() {
return {
title: "Компоненты Vue" // Текст заголовка
}
}
}
</script>
Здесь вы видите типичный шаблон для Vue‑проектов с Vite.
Основы Composition API - альтернативный стиль
Vue 3 предлагает новый способ описания логики — Composition API. На старте достаточно понять, как выглядит самый простой пример.
Вот как можно переписать счетчик с помощью setup и ref:
<div id="app">
<p>Счетчик - {{ counter }}</p>
<button @click="increment">
Увеличить
</button>
</div>
const { createApp, ref } = Vue // Деструктурируем нужные функции из Vue
const app = createApp({
setup() {
const counter = ref(0) // Создаем реактивную переменную
const increment = () => {
// Изменяем значение через свойство value
counter.value++
}
// Возвращаем значения и функции, чтобы использовать их в шаблоне
return {
counter,
increment
}
}
})
app.mount("#app")
Кратко:
ref(0)создает реактивное значение;- в JavaScript коде вы обращаетесь через
counter.value; - в шаблоне пишете
{{ counter }}— Vue сам развернет.value.
Этот стиль полезен, когда логика становится сложнее и вы хотите группировать ее по функциональным блокам, а не по полям data, methods и так далее. На начальном этапе достаточно знать, что такой подход существует и чем он внешне отличается.
Заключение
Вы прошли через основные части, которые нужно понимать, чтобы уверенно двигаться с Vue дальше:
- запуск приложения через CDN и через Vite;
- реактивный объект
dataи синхронизация с шаблонами; - интерполяция и привязка атрибутов с
v-bind; - ключевые директивы
v-if,v-show,v-for,v-on; - двустороннее связывание
v-modelдля разных типов полей; - вычисляемые свойства
computedи наблюдателиwatch; - базовая работа с компонентами, props и событиями;
- пример однофайловых компонентов и краткое знакомство с Composition API.
Если вы уверенно чувствуете себя с этими основами, переход к маршрутизации (Vue Router), управлению состоянием (Pinia) и более сложным паттернам будет намного проще. На этом фундаменте строится практически любой Vue‑проект.
Частозадаваемые технические вопросы по теме и ответы
Как правильно организовать структуру проекта на Vue при старте
На небольшом проекте достаточно такой структуры:
- src
- main.js — точка входа
- App.vue — корневой компонент
- components — папка с переиспользуемыми компонентами
- views — крупные страницы, если сразу планируете маршрутизацию
- assets — стили, картинки
Дальше по мере роста можно добавлять папки для сервисов, стора, утилит.
Как подключить стороннюю библиотеку (например, axios) в проект Vue
Установите пакет:
npm install axiosИмпортируйте и используйте в компонентах:
import axios from "axios" export default { async mounted() { // Здесь мы делаем запрос после монтирования компонента const response = await axios.get("/api/data") console.log(response.data) } }
Можно также создать отдельный файл api.js, где настроить базовый URL и интерсепторы, и импортировать его в компоненты.
Как сделать глобальный компонент в проекте на Vite
В main.js вы можете зарегистрировать компонент глобально:
import { createApp } from "vue"
import App from "./App.vue"
import BaseButton from "./components/BaseButton.vue"
const app = createApp(App)
// Регистрируем компонент глобально
app.component("BaseButton", BaseButton)
app.mount("#app")
Теперь BaseButton доступен в любом компоненте без локальной регистрации.
Как добавить стили только к одному компоненту
В SFC используйте атрибут scoped:
<style scoped>
.button {
/* Эти стили применятся только внутри этого компонента */
background-color: blue;
}
</style>
Vue добавит специальные атрибуты к селекторам и элементам, чтобы стили не утекали наружу.
Как подключить Vue Devtools для отладки
- Установите расширение Vue Devtools для вашего браузера (Chrome или Firefox).
- Запускайте приложение в режиме разработки (
npm run dev). - Откройте инструменты разработчика — появится вкладка Vue, где вы увидите дерево компонентов, состояние, props и события.
Если используете Vue через CDN, убедитесь, что подключаете не production‑версию, а глобальную dev‑сборку, как в примере сvue.global.js.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев