Vue — прогрессивный JavaScript-фреймворк для создания пользовательских интерфейсов. Вы описываете, как интерфейс должен выглядеть при текущих данных, а Vue сам обновляет страницу, когда данные меняются. Его можно подключить к готовому сайту одним тегом <script> или построить на нём большое одностраничное приложение (SPA).
Проблема: ручная работа с DOM
Возьмём счётчик: число кликов, подсказку, которая меняется после пятого клика, и кнопку «Reset», неактивную, пока счётчик равен нулю. На чистом JavaScript:
<p>Clicks: <span id="count">0</span></p>
<p id="hint">Click the button</p>
<button id="inc">+1</button>
<button id="reset" disabled>Reset</button>
<script>
let count = 0
const countEl = document.querySelector('#count')
const hintEl = document.querySelector('#hint')
const resetBtn = document.querySelector('#reset')
// после каждого изменения данных вручную обновляем все зависимые места
function render() {
countEl.textContent = count
hintEl.textContent = count >= 5 ? 'Enough clicking!' : 'Click the button'
resetBtn.disabled = count === 0
}
document.querySelector('#inc').addEventListener('click', () => {
count++
render()
})
resetBtn.addEventListener('click', () => {
count = 0
render()
})
</script>Код работает, но в нём много служебной работы: найти элементы, повесить обработчики, после каждого изменения вызвать render() и обновить в нём все зависимые места. Это императивный подход: вы командуете браузеру, как изменить страницу. Когда элементов сотни, а данные меняются из многих мест, легко забыть обновить один из них — и интерфейс покажет неправду.
Декларативный UI: тот же счётчик на Vue
Vue предлагает декларативный подход: вы один раз описываете, что должно быть на экране в зависимости от данных, а синхронизацию берёт на себя фреймворк. Коротко: UI = f(state).
<div id="app">
<p>Clicks: {{ count }}</p>
<p>{{ count >= 5 ? 'Enough clicking!' : 'Click the button' }}</p>
<button @click="count++">+1</button>
<button :disabled="count === 0" @click="count = 0">Reset</button>
</div>
<script type="module">
import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
createApp({
setup() {
const count = ref(0) // реактивное значение: Vue следит за его изменениями
return { count } // делаем count доступным в шаблоне
}
}).mount('#app') // Vue управляет содержимым <div id="app">
</script>Разметка внутри #app стала шаблоном: {{ count }} выводит значение, @click вешает обработчик, :disabled привязывает атрибут к выражению (подробно — в разделе о шаблонах). ref(0) создаёт реактивное значение: когда count меняется, Vue сам обновляет зависящие места. После пяти кликов DOM выглядит так:
<div id="app" data-v-app="">
<p>Clicks: 5</p>
<p>Enough clicking!</p>
<button>+1</button>
<button>Reset</button>
</div>Кнопка потеряла disabled, подсказка сменилась — без отдельного кода (data-v-app — метка контейнера приложения).
💡 Сохраните пример в
index.htmlи откройте в браузере — установка не нужна. Онлайн-песочница: Vue Playground.
«Прогрессивный» фреймворк
«Прогрессивный» значит, что Vue внедряют постепенно, в нужном проекту объёме:
| Уровень | Как подключается | Когда подходит |
|---|---|---|
| Виджет на странице | <script type="module"> с CDN | калькулятор или фильтр на готовом сайте (Laravel, WordPress) |
| SPA | проект на Vite, файлы .vue | личный кабинет, админ-панель |
| SPA + экосистема | + Vue Router, Pinia | много страниц и общего состояния |
| Nuxt | мета-фреймворк поверх Vue | SEO, серверный рендеринг |
Первый уровень: Vue «оживляет» один блок на обычной странице, которую сгенерировал сервер.
<main>
<h1>Delivery</h1>
<!-- обычный HTML страницы: Vue его не трогает -->
<p>Free delivery for orders over 50 AZN.</p>
<div id="delivery-calc">
<label>Weight: <input v-model.number="weight" type="number" min="0"> kg</label>
<p>Price: {{ price }} AZN</p>
</div>
</main>
<script type="importmap">
{ "imports": { "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.prod.js" } }
</script>
<script type="module">
import { createApp, ref, computed } from 'vue'
createApp({
setup() {
const weight = ref(1)
const price = computed(() => 5 + weight.value * 2) // 5 AZN + 2 AZN за кг
return { weight, price }
}
}).mount('#delivery-calc')
</script>Vue управляет только #delivery-calc: введите вес 3 — и цена станет 11 AZN. Import map позволяет писать from 'vue' вместо полного URL, .prod.js — production-сборка. v-model и computed изучим позже.
В SPA код переезжает в однофайловые компоненты — файлы .vue, где шаблон, логика и стили лежат вместе:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<p>Clicks: {{ count }}</p>
<button @click="count++">+1</button>
<button :disabled="count === 0" @click="count = 0">Reset</button>
</template>Шаблон тот же, а <script setup> избавляет от createApp и return: всё объявленное в скрипте доступно в шаблоне. Файлы .vue компилирует Vite — так мы будем писать весь курс.
Vue среди других фреймворков
Все четыре решения строят декларативный UI из компонентов, но с разной философией:
| Vue | React | Angular | Svelte | |
|---|---|---|---|---|
| Что это | прогрессивный фреймворк | библиотека для UI | платформа «всё включено» | фреймворк-компилятор |
| Разметка | HTML-шаблоны (JSX по желанию) | JSX | HTML-шаблоны | HTML-шаблоны |
| Реактивность | ref/reactive на Proxy | useState, перерисовка компонента | Signals | руны $state |
| Роутинг и состояние | официальные Vue Router и Pinia | сторонние библиотеки | встроены | SvelteKit |
| Мета-фреймворк | Nuxt | Next.js | Angular SSR | SvelteKit |
Сильные стороны Vue — шаблоны на привычном HTML, точечная реактивность, отличная документация и официальные инструменты. Он популярен в экосистеме Laravel, на нём написан интерфейс GitLab. Знания переносимы: компоненты и реактивность устроены похоже во всех четырёх фреймворках.
Экосистема и актуальная версия
| Инструмент | Для чего | Где в курсе |
|---|---|---|
| Vite | dev-сервер и сборка | следующая тема |
| Vue Router | страницы и навигация в SPA | раздел «Маршрутизация» |
| Pinia | общее хранилище состояния | раздел «Pinia» |
| VueUse | сотни готовых composables | раздел «Composables» |
| Nuxt | SSR, SEO, файловые маршруты | последний раздел |
Актуальная ветка — Vue 3.5 (на момент написания 3.5.43); версия 3.6 с режимом рендеринга Vapor Mode — в статусе Release Candidate. Vue 2 не поддерживается с 31 декабря 2023 года, но примеров для него в сети много.
Типичные ошибки
Менять DOM вручную внутри Vue-приложения. document.querySelector('#count').textContent = 10 сработает лишь до следующего обновления — Vue перерисует элемент по своим данным. Меняйте данные (count.value = 10), а не DOM.
Забыть type="module". Обычный скрипт не понимает import, и браузер выдаст Uncaught SyntaxError: Cannot use import statement outside a module.
<script>import { createApp } from 'vue'</script> <!-- ❌ -->
<script type="module">import { createApp } from 'vue'</script> <!-- ✅ -->Копировать примеры для Vue 2. new Vue({ el, data }), Vue.component(), фильтры {{ price | currency }} и $on в Vue 3 не работают. Ищите в примерах createApp.
Оставить dev-сборку на рабочем сайте. vue.esm-browser.js тяжелее и пишет в консоль «You are running a development build of Vue». В продакшне нужен vue.esm-browser.prod.js, а для приложения — проект на Vite.
Итог
- Vue — фреймворк для декларативного UI: вы описываете результат по данным, а DOM обновляет Vue.
ref()создаёт реактивное значение, шаблон с{{ }},@clickи:disabledописывает разметку,createApp().mount()запускает приложение.- «Прогрессивный» — значит внедряемый постепенно: от виджета через CDN до SPA на Vite и Nuxt.
- У Vue HTML-шаблоны, точечная реактивность и официальная экосистема: Vite, Vue Router, Pinia, VueUse, Nuxt.
- Актуальная версия — Vue 3.5; код с
new Vue()относится к устаревшему Vue 2.
Практика
- Откройте Vue Playground, измените в примере выводимое значение и посмотрите, как обновился результат справа.
- Сохраните счётчик на Vue в
index.htmlи откройте в браузере. Добавьте кнопку «-1», неактивную при нуле. - Добавьте в обе версии счётчика (JS и Vue) строку «Even»/«Odd» — чётное или нечётное число кликов. Сравните, сколько строк пришлось изменить в каждой.
- Сделайте виджет «Калькулятор чаевых» для страницы кафе: поле суммы, поле процента и строка «Tip: … AZN, Total: … AZN». Vue управляет только блоком
#tip-calc.
💡 В задании 4 используйте import map и
computed, как в примере с доставкой, иv-model.number, иначе значения из полей придут строками.