Перейти к основному содержимому

Первая программа на Vue.js

Разработчику Архитектору
Загрузка симулятора первой программы…

Первая программа на Vue.js

Где применяют Vue

Vue — фреймворк для интерфейса в браузере. Вы описываете страницу почти обычным HTML, а Vue сам обновляет экран, когда меняются данные. Ручной поиск элементов в DOM и textContent = … для каждой кнопки не нужен — это и есть реактивность.

В React разметка чаще живёт в JS (JSX). Во Vue привычнее шаблон <template> с директивами: v-model, v-if, v-for, @click. После HTML/CSS многим так проще войти в SPA.


Рекомендуемый порядок

Если вы только заходите во Vue, держитесь последовательности:

  1. ref и простые кнопки.
  2. v-model и v-if.
  3. v-for со списком.
  4. onMounted + fetch.

Так вы по шагам закрываете весь базовый цикл клиентского приложения.

В этой статье одно приложение разберём по шагам:

  1. Счётчик и имяref, директивы, стили в .vue.
  2. Список заметок с Node APIonMounted и fetch.
  3. Дочерний компонент — props и события вверх.
  4. Options API — как пишут в legacy-проектах.

Обзор экосистемы: Vue.js. Склейка с API: Fullstack 264. Сравнение: React 272 · Angular 292.


Что получится

ЧастьРезультат
Проектcreate vue + Vite → http://localhost:5173
UIПриветствие, счётчик, (опционально) список с сервера
ФайлыApp.vue и при желании components/Counter.vue
APIGET http://127.0.0.1:3000/notes, если Node запущен

Создание проекта

Нужен Node.js (LTS с nodejs.org):

node -v
npm -v

Разбор:

  • node -v показывает установленную версию Node.js и проверяет, что среда выполнения JavaScript доступна в системе.
  • npm -v выводит версию пакетного менеджера npm, через который устанавливаются зависимости Vue-проекта.
  • Команды помогают сразу выявить проблему окружения до создания проекта, а не в середине установки.
  • Если одна из команд не находится, нужно добавить Node.js в PATH или переустановить LTS-версию.
npm create vue@latest my-first-vue-app
cd my-first-vue-app
npm install
npm run dev

Разбор:

  • npm create vue@latest my-first-vue-app генерирует каркас приложения Vue 3 с актуальными шаблонами.
  • cd my-first-vue-app открывает контекст проекта, чтобы установка и запуск выполнялись по нужным путям.
  • npm install подтягивает все библиотеки, включая vue, vite и служебные dev-зависимости.
  • npm run dev запускает локальный сервер разработки; адрес обычно выводится в терминале.
  • На этом шаге формируется полный цикл локальной разработки: создать, установить, запустить.
Вопрос мастераНа первый раз
TypeScriptпо желанию (ниже — JS)
JSXNo
Vue RouterYes
PiniaNo
Vitest / CypressNo

После npm run dev откройте адрес из терминала (обычно 5173). Сохранили файл — страница обновилась (HMR).

Структура

src/App.vue — корень. src/components/ — ваши блоки UI. src/main.js монтирует приложение в #app.


Файл .vue — три блока

<script setup> ← логика
<template> ← разметка + директивы
<style scoped> ← стили только этого компонента

Разбор:

  • <script setup> хранит реактивное состояние, функции и импорты компонента.
  • <template> описывает структуру интерфейса и связывает её с данными через интерполяцию и директивы.
  • <style scoped> ограничивает CSS текущим компонентом, чтобы стили не протекали в соседние части приложения.
  • Такое разделение делает файл .vue самодостаточным: логика, разметка и стиль живут рядом и проще поддерживаются.

Composition API и <script setup>

Полный src/App.vue:

<script setup>

import { ref } from 'vue'

const count = ref(0)
const name = ref('')

const increment = () => { count.value++ }
const decrement = () => { count.value-- }
const reset = () => { count.value = 0 }
</script>

<template>
<div class="app">
<h1>Моя первая программа на Vue.js</h1>

<section class="greeting">
<input v-model="name" type="text" placeholder="Введите ваше имя" />
<h2 v-if="name">Привет, {{ name }}!</h2>
</section>

<section class="counter">
<h2>Счётчик: {{ count }}</h2>
<div class="buttons">
<button type="button" @click="decrement">−</button>
<button type="button" @click="reset">Сброс</button>
<button type="button" @click="increment">+</button>
</div>
</section>
</div>
</template>

<style scoped>
.app { text-align: center; padding: 2rem; font-family: system-ui, sans-serif; }
.greeting { margin: 1.5rem 0; padding: 1.25rem; background: #f0f0f0; border-radius: 10px; }
.counter { margin: 1.5rem 0; padding: 1.25rem; background: #e8f4f8; border-radius: 10px; }
input { padding: 0.5rem 0.75rem; font-size: 1rem; border: 2px solid #ddd; border-radius: 6px; }
button {
margin: 0 4px; padding: 0.5rem 1rem; font-size: 1rem;
background: #42b883; color: #fff; border: none; border-radius: 6px; cursor: pointer;
}
button:hover { background: #3aa876; }
</style>

Разбор — скрипт

КодСмысл
ref(0)Реактивная ячейка; в script меняем через .value
\&#123;\&#123; count \&#125;\&#125; в templateVue разворачивает ref сам
() => count.value++Обработчик клика — функция, не вызов сразу

Разбор — шаблон

СинтаксисРоль
\&#123;\&#123; name \&#125;\&#125;Подстановка текста
v-model="name"Двусторонняя связь с input
v-if="name"Показать блок, если имя не пустое
@click="increment"Событие клика (v-on:click)
.value

В <script setup> пишут count.value++. В шаблоне — просто {'{{ count }}'}.


computed

Производное значение без ручной синхронизации:

<script setup>

import { ref, computed } from 'vue'

const count = ref(0)
const doubled = computed(() => count.value * 2)
</script>

<template>
<p>Счётчик: {{ count }}, удвоенный: {{ doubled }}</p>
</template>

Разбор:

  • computed(() => count.value * 2) создаёт производное состояние, которое кешируется и пересчитывается только при изменении count.
  • В шаблоне &#123;&#123; doubled &#125;&#125; используется как обычная переменная, хотя в скрипте это вычисляемый ref.
  • Подход избавляет от ручной синхронизации между count и его производными значениями.
  • Шаблон остаётся декларативным: он только отображает состояние, а формулы находятся в script.

computed пересчитывается только когда меняются зависимости.


onMounted и заметки с API

  1. API "Заметки" на порту 3000.
  2. CORS или прокси — 263, 264.
<script setup>

import { ref, onMounted } from 'vue'

const notes = ref([])
const error = ref('')
const loading = ref(true)

onMounted(async () => {
try {
const res = await fetch('http://127.0.0.1:3000/notes')
if (!res.ok) throw new Error(`HTTP ${res.status}`)
notes.value = await res.json()
} catch (e) {
error.value = e.message
} finally {
loading.value = false
}
})
</script>

<template>
<section>
<h2>Заметки с сервера</h2>
<p v-if="loading">Загрузка…</p>
<p v-else-if="error">Ошибка: {{ error }}</p>
<ul v-else>
<li v-for="n in notes" :key="n.id">{{ n.text }}</li>
</ul>
</section>
</template>

Разбор:

  • onMounted(async () => { ... }) запускает асинхронный код после монтирования компонента и готовности UI.
  • fetch('http://127.0.0.1:3000/notes') запрашивает данные API; res.ok проверяет успешность HTTP-ответа.
  • notes.value = await res.json() сохраняет полученный массив в реактивное состояние для отрисовки списка.
  • Блок try/catch/finally разделяет успешный сценарий, ошибку и финальное выключение loading.
  • В шаблоне v-if / v-else-if / v-else управляет тремя состояниями интерфейса: загрузка, ошибка, данные.
ЭлементЗачем
onMountedКод после появления компонента на странице
v-for + :keyСписок со стабильными id
v-if / v-else-ifСостояния загрузки и ошибки

Дочерний компонент

src/components/Counter.vue:

<script setup>
defineProps({ value: { type: Number, required: true } })
const emit = defineEmits(['increment', 'decrement', 'reset'])
</script>

<template>
<section class="counter">
<h2>Счётчик: {{ value }}</h2>
<button type="button" @click="emit('decrement')">−</button>
<button type="button" @click="emit('reset')">Сброс</button>
<button type="button" @click="emit('increment')">+</button>
</section>
</template>

Разбор:

  • defineProps(...) объявляет входные данные компонента и валидирует обязательность value.
  • defineEmits(['increment', 'decrement', 'reset']) фиксирует контракт событий, которые компонент отправляет родителю.
  • emit('...') в обработчике кнопки передаёт намерение действия наверх, не меняя состояние напрямую.
  • Компонент остаётся переиспользуемым: он отображает значение и генерирует события, а бизнес-логика хранится у родителя.

App.vue:

<script setup>

import { ref } from 'vue'
import Counter from './components/Counter.vue'

const count = ref(0)
</script>

<template>
<Counter
:value="count"
@increment="count++"
@decrement="count--"
@reset="count = 0"
/>
</template>

Разбор:

  • import Counter from './components/Counter.vue' подключает дочерний компонент в родительский App.vue.
  • :value="count" передаёт реактивное значение вниз через props.
  • @increment, @decrement, @reset подписывают родитель на события ребёнка и изменяют count в одном источнике истины.
  • Паттерн формирует предсказуемую архитектуру: данные вниз, события вверх.

Props вниз, события вверх — как в React.


Options API — устаревший стиль

<script>
export default {
data() {
return { count: 0, name: '' }
},
methods: {
increment() { this.count++ },
decrement() { this.count-- },
reset() { this.count = 0 },
},
}
</script>

<template>
<h2>Счётчик: {{ count }}</h2>
<input v-model="name" />
<button type="button" @click="increment">+</button>
</template>

Разбор:

  • data() возвращает начальное состояние компонента в стиле Options API.
  • В methods размещаются обработчики, которые вызываются из шаблона через @click.
  • this.count++ и this.count-- меняют реактивное поле экземпляра компонента.
  • Пример иллюстрирует legacy-стиль Vue 2/раннего Vue 3, который часто встречается в существующих кодовых базах.
Options APIComposition API
data()ref() / reactive()
this.count++count.value++
Учебные и старые кодовые базыНовые проекты, composables

Стили scoped

<style scoped> ограничивает CSS этим компонентом. Цвет #42b883 — фирменный зелёный Vue.


Краткий обзор концепций Vue

Реактивность связывает данные и DOM. Шаблон описывает, что показать. Директивы v-* добавляют поведение. Компоненты делят UI на части.


Практика — что добавить самому

  • Кнопка ×2: count.value *= 2.
  • Todo на ref([]) и v-for.
  • Таймер: setInterval в onMounted, clearInterval в onUnmounted.
  • POST заметки: fetch с method: 'POST' и JSON.stringify — шаблоны с разбором в Fetch / axios — типовые запросы.

Запуск

  1. Node.js LTS.
  2. Установка и dev-сервер (npm create vue@latestnpm installnpm run dev):
npm create vue@latest
npm install
npm run dev

Разбор:

  • Первая команда создаёт новый проект Vue через официальный генератор.
  • Вторая устанавливает зависимости, необходимые для сборки и запуска.
  • Третья поднимает dev-сервер с наблюдением за файлами.
  • Три команды покрывают базовую инициализацию проекта с нуля.
  1. Вставить код в App.vue.
  2. (Опционально) второй терминал — API 262.
  3. Production: npm run build → каталог dist/.
npm run build

Разбор:

  • Команда запускает production-сборку и генерирует оптимизированные файлы в dist/.
  • На этапе сборки выполняются минификация, разделение чанков и удаление неиспользуемого кода.
  • Результат готов к публикации на статический хостинг или интеграции в CI/CD-пайплайн.
  • Ошибки сборки на этом шаге обычно сигнализируют о проблемах импорта, типов или конфигурации.

Частые ошибки

СимптомПричина
Счётчик не двигаетсяНет .value в script
CORS264
Список без keyПредупреждения Vue при v-for

Что попробовать

  1. React 272 на том же API — галерея компонентов для сравнения синтаксиса.
  2. Vue Router — страница /about.
  3. Next 2731 для SSR.

Связанные материалы

ТемаМатериал
Обзор28.md
Галерея примеровVue и Svelte — лаборатория
Fullstack264.md
Чек-лист999.md

Vue удобен тем, что первая программа похожа на вёрстку, а дальше тот же проект растёт до SPA с API и тестами.


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.