
⚡ Изучаем основы Vue 3: от пустой папки до компонентов
Вы смотрите на Vue 3 и видите крутой фронтенд-фреймворк из большой тройки. React, Angular, Vue, звучит серьёзно. А потом открываете первое руководство, а там ждут vite, create-vue и .vue-файлы с транспайлерами... Хотя на самом деле Vue можно подключить одной строчкой <script> и писать код прямо сейчас. Без сборщиков, без JSX, без ада настройки.
Разрыв между «хочу попробовать» и «я написал компонент» здесь короче, чем где-либо ещё. Но большинство уроков этот разрыв не сокращают, они пересказывают документацию фрагментами и не дают цельной картины. Вы видите разрозненные куски синтаксиса и не понимаете, как они превращаются в приложение.
Это руководство, ровно тот путь, который мы сами прошли с Vue: от пустой папки до компонентов с props. Вы напишете и запустите каждую строчку сами. Никакой магии, только работающий код и понимание реактивности, главной фишки Vue.
💡 Быстрый обзор:
- Собираем приложение Vue на CDN и монтируем в DOM: ваш первый работающий экземпляр за 5 минут
- Проходим четыре ключевые директивы, v-for, v-if, v-on и v-bind: столпы любого Vue-шаблона
- Разбираем computed и methods: когда кешировать, а когда вызывать
- Выносим логику в компоненты и прокидываем данные через props: шаг к реальной архитектуре
Создаём приложение Vue
Начнём с абсолютного нуля. Никаких create-vue, vite или vue-cli, просто папка с двумя файлами.
Создайте директорию и файлы в терминале:
1 mkdir vue3-intro 2 cd vue3-intro 3 touch index.html app.js
Или сделайте то же самое через проводник. Теперь откройте index.html и напишите каркас:
1 <!DOCTYPE html> 2 <html lang="ru"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> 7 <title>Vue 3 — первые шаги</title> 8 </head> 9 <body> 10 <div id="app"></div> 11 <script src="./app.js"></script> 12 </body> 13 </html>
Три ключевые строки. <script src="https://unpkg.com/vue@3/dist/vue.global.js"> подключает Vue 3 из CDN. Это самый быстрый способ старта: библиотека грузится как обычный JavaScript-файл, ничего собирать не нужно. <div id="app"> задаёт точку монтирования: место в DOM, где Vue будет «жить». Третья строка импортирует app.js, в котором мы опишем логику.
Теперь наполним app.js:
1 const app = Vue.createApp({ 2 data() { 3 return { 4 course: 'Введение во Vue 3' 5 } 6 } 7 }) 8 9 app.mount('#app')
Метод Vue.createApp() создаёт экземпляр приложения. Ему передаётся объект с настройками, здесь мы объявили свойство course через функцию data(). Vue делает каждое возвращённое из data() свойство реактивным: при изменении значения DOM обновится автоматически. Никаких ручных манипуляций с деревом.
Но приложение нужно «прикрепить» к элементу на странице. Это делает app.mount('#app'). Без этой строки Vue не будет реагировать ни на какие изменения, экземпляр создан, но не запущен.
Осталось отобразить свойство в HTML. Внутри <div id="app"> напишите:
1 <h1>{{ course }}</h1>
Двойные фигурные скобки {{ }}, это интерполяция, основной способ вывода данных в шаблонах Vue. Всё, что внутри {{ }}, Vue заменяет значением свойства из data().

Откройте index.html в браузере. На странице надпись «Введение во Vue 3». Первое приложение готово, и вы написали его сами, а не сгенерировали через CLI.
Отображение списков
В реальном приложении вы почти всегда работаете с массивами: товары, теги, посты. Vue даёт для этого директиву v-for.
Добавим в data() массив тегов:
1 const app = Vue.createApp({ 2 data() { 3 return { 4 course: 'Введение во Vue 3', 5 description: 'Базовое руководство по Vue 3 для новичков', 6 price: '$19.99', 7 tags: ['Vue', 'Frontend', 'JavaScript'] 8 } 9 } 10 })
А в HTML, список:
1 <ul> 2 <li v-for="tag in tags">{{ tag }}</li> 3 </ul>
Директива v-for перебирает массив tags и для каждого элемента создаёт свой <li>. Переменная tag внутри цикла, это текущий элемент. Результат выглядит так:

Важный нюанс: при обходе массива каждому элементу DOM нужен уникальный ключ. Vue использует его, чтобы отслеживать идентичность узлов при обновлениях, это ускоряет перерисовку и предотвращает баги с состоянием.
1 <ul> 2 <li v-for="(tag, index) in tags" :key="index">{{ tag }}</li> 3 </ul>
:key, это сокращение для v-bind:key, которое привязывает атрибут key к значению индекса. Если у вас массив объектов, например { id: 1, name: 'Vue' }, в качестве ключа берите id, а не индекс. Индекс меняется при перестановках, id же стабилен, это надёжнее.
Условный рендеринг
Часто нужно показать или скрыть элемент в зависимости от условия: кнопку покупки, только если курс доступен. Vue делает это директивой v-if.
Добавим флаг available в data():
1 data() { 2 return { 3 course: 'Введение во Vue 3', 4 description: 'Базовое руководство по Vue 3 для новичков', 5 price: '$19.99', 6 available: true, 7 tags: [ 8 { id: 1, name: 'Vue' }, 9 { id: 2, name: 'Frontend' }, 10 { id: 3, name: 'JavaScript' } 11 ] 12 } 13 }
И используем в разметке:
1 <p v-if="available">Курс доступен к покупке!</p> 2 <p v-else>К сожалению, курс сейчас недоступен.</p>
v-if вычисляет выражение в кавычках. Если оно истинно, рендерится первый блок, иначе, v-else. Попробуйте в консоли браузера выполнить app.available = false и посмотрите, как страница отреагирует мгновенно.

Кроме v-if / v-else есть ещё v-else-if для цепочек условий и v-show. Разница принципиальная: v-if полностью удаляет элемент из DOM, а v-show только переключает display: none. Выбирайте v-if, когда условие меняется редко. v-show, для частых переключений вроде табов, где важна производительность.
Обработка событий
Директива v-on, по-простому @, слушает DOM-события: клики, ввод, наведение. Событие произошло, Vue запускает указанный метод или выражение.
Добавим метод purchase в настройки приложения:
1 const app = Vue.createApp({ 2 data() { 3 return { 4 course: 'Введение во Vue 3', 5 description: 'Базовое руководство по Vue 3 для новичков', 6 price: '$19.99', 7 available: true, 8 tags: [ 9 { id: 1, name: 'Vue' }, 10 { id: 2, name: 'Frontend' }, 11 { id: 3, name: 'JavaScript' } 12 ] 13 } 14 }, 15 methods: { 16 purchase() { 17 console.log('Курс куплен!') 18 } 19 } 20 })
Секция methods содержит функции, доступные в шаблоне и других методах через this. Теперь повесим вызов на кнопку:
1 <button @click="purchase">Купить</button>

Для простых действий можно писать код прямо в @click:
1 <button @click="available = !available">Переключить доступность</button>

Такой инлайн-код удобен для коротких операций вроде переключения флага. Но сложную логику всегда выносите в методы, шаблон должен оставаться читаемым. Правило простое: counter += 1, ок в шаблоне, десять строк логики покупки, в метод.
Привязка атрибутов
Атрибуты вроде src, href или alt часто должны быть динамическими. Директива v-bind, по-простому :, связывает значение атрибута с выражением из Vue.
Добавим данные для изображения:
1 data() { 2 return { 3 // ...остальные свойства 4 imgURL: 'https://catalins.tech/img', 5 imgDescription: 'Фотография рабочего стола с компьютером' 6 } 7 }
В шаблоне привяжем их к <img>:
1 <img :src="imgURL" :alt="imgDescription" width="500" height="350">
Развёрнутая запись выглядела бы как v-bind:src="imgURL", двоеточие это просто синтаксический сахар. Vue теперь отслеживает imgURL и imgDescription: измените их в data(), и картинка на странице обновится автоматически. Это и есть реактивность в действии.

Тот же принцип применим к атрибутам вроде :href, :disabled и :class, а также к любым другим. Попробуйте сами: добавьте ссылку с динамическим href из data() и убедитесь, что работает без единой лишней строчки.
Вычисляемые свойства
Иногда данные нужно преобразовать перед показом: отформатировать цену, склеить имя и фамилию, проверить условие. Для этого в Vue есть computed, вычисляемые свойства.
Представьте, что вы хотите всегда иметь описание изображения, даже если поле imgDescription пустое. Пишем вычисляемое свойство:
1 const app = Vue.createApp({ 2 data() { 3 return { 4 course: 'Введение во Vue 3', 5 description: 'Базовое руководство по Vue 3 для новичков', 6 price: '$19.99', 7 available: true, 8 imgURL: 'https://catalins.tech/img', 9 imgDescription: 'Фотография рабочего стола с компьютером', 10 tags: [ 11 { id: 1, name: 'Vue' }, 12 { id: 2, name: 'Frontend' }, 13 { id: 3, name: 'JavaScript' } 14 ] 15 } 16 }, 17 computed: { 18 hasImageDescription() { 19 return this.imgDescription.length > 0 20 ? this.imgDescription 21 : 'Автоматическое описание изображения' 22 } 23 } 24 })
Теперь в шаблоне используем вычисляемое свойство вместо сырых данных:
1 <img :src="imgURL" :alt="hasImageDescription" width="500" height="350">
Vue кеширует результат computed-свойства и пересчитывает его только тогда, когда меняются зависимости, в нашем случае imgDescription. Это эффективнее, чем вызывать функцию при каждом рендере.
Важное правило: вычисляемые свойства предназначены для отображения данных, а не для их изменения. Не меняйте состояние приложения внутри computed, для этого есть методы.
Методы против вычисляемых свойств
Разница между methods и computed сводится к одному вопросу: вы меняете данные или отображаете производное значение?
Вычисляемые свойства, для уровня представления. Форматирование, фильтрация, проверка условий, склейка строк. Они кешируются и не имеют побочных эффектов.
Методы, для действий. Изменить значение в data(), отправить запрос, переключить флаг. Они вызываются явно и могут делать что угодно.
Типичный антипаттерн, изменять this.course внутри computed. Вычисляемое свойство реагирует на изменение course, но не должно менять его само. Для изменения данных всегда берите метод. Шаблон пусть описывает «что показывать», а не «как вычислять».
Компоненты и props
Когда приложение разрастается, держать всё в одном файле становится невозможно. Vue позволяет разбить интерфейс на компоненты, изолированные блоки со своей логикой, шаблоном и стилями.
Создадим первый компонент CourseCard:
1 mkdir components 2 touch components/CourseCard.js
В components/CourseCard.js опишем компонент:
1 app.component('coursedisplay', { 2 data() { 3 return { 4 course: 'Введение во Vue 3', 5 description: 'Базовое руководство по Vue 3 для новичков', 6 price: '$19.99', 7 available: true, 8 imgURL: 'https://catalins.tech/img', 9 imgDescription: 'Фотография рабочего стола с компьютером', 10 tags: [ 11 { id: 1, name: 'Vue' }, 12 { id: 2, name: 'Frontend' }, 13 { id: 3, name: 'JavaScript' } 14 ] 15 } 16 }, 17 computed: { 18 hasImageDescription() { 19 return this.imgDescription.length > 0 20 ? this.imgDescription 21 : 'Автоматическое описание изображения' 22 } 23 }, 24 template: ` 25 <img :src="imgURL" :alt="hasImageDescription" width="500" height="350"> 26 <h1>{{ course }}</h1> 27 <p>{{ description }}</p> 28 <p>{{ price }}</p> 29 <button @click="available = !available">Купить</button> 30 <ul> 31 <li v-for="tag in tags" :key="tag.id">{{ tag.name }}</li> 32 </ul> 33 <p v-if="available">Курс доступен к покупке!</p> 34 <p v-else>К сожалению, курс сейчас недоступен.</p> 35 ` 36 })
Первый аргумент app.component(), имя, которое мы будем использовать в HTML как тег: <coursedisplay></coursedisplay>. Второй, объект с data(), computed и template, это «начинка» компонента.
Теперь подключим компонент в index.html и используем его:
1 <div id="app"> 2 <coursedisplay></coursedisplay> 3 </div> 4 <script src="./app.js"></script> 5 <script src="./components/CourseCard.js"></script> 6 <script> 7 const mountedApp = app.mount('#app') 8 </script>
Мы импортировали файл компонента, использовали его как пользовательский тег и смонтировали приложение прямо в HTML. Логика курса теперь заперта внутри CourseCard, а app.js отвечает только за создание экземпляра.
Props: передача данных между компонентами
Props, сокращение от properties, механизм передачи данных от родительского компонента к дочернему. Дочерний компонент объявляет, какие props он принимает, а родитель передаёт их как атрибуты.
Добавим в CourseCard prop paid, который показывает, платный курс или бесплатный:
1 app.component('coursedisplay', { 2 props: { 3 paid: { 4 type: Boolean 5 } 6 }, 7 // ...остальная конфигурация 8 })
Родитель, в нашем случае app.js, передаёт значение:
1 const app = Vue.createApp({ 2 data() { 3 return { 4 allCourses: 1, 5 paid: true 6 } 7 } 8 })
И в index.html пробрасываем prop:
1 <coursedisplay :paid="paid"></coursedisplay>
Vue автоматически проверяет тип переданного значения. Если ожидается Boolean, а пришла строка, фреймворк выдаст предупреждение в консоли. Это спасает от глупых ошибок при росте кодовой базы.
Чтобы prop стал обязательным, добавьте required: true в его описание. Тогда Vue будет ругаться, если родитель не передал значение, вы узнаете о проблеме до того, как она сломает интерфейс.
Полный код руководства вы найдёте в репозитории vue3-intro на GitHub, там лежат готовые файлы index.html, app.js и components/CourseCard.js. Форкните и экспериментируйте.
Видео по теме
Чтобы закрепить материал на практике, посмотрите первый урок из курса Net Ninja по Vue 3, он отлично дополняет это руководство живым показом кода:
⁉️🤔 Частые вопросы
Можно ли использовать Vue 3 без сборщика в продакшене?
Да, подключение через CDN (
unpkg.com/vue@3/dist/vue.global.js) работает и в продакшене. Но для реальных проектов мы рекомендуем перейти на Vite: вы получите tree-shaking, code-splitting и Single-File Components, которые резко упрощают разработку при росте приложения. Стартовый шаблонnpm create vue@latestсобирает всё это за минуту.
Чем v-if отличается от v-show?
v-ifполностью удаляет элемент из DOM, когда условие ложно.v-showоставляет элемент в DOM, но переключает CSS-свойствоdisplay: none. Используйтеv-if, когда условие меняется редко, например, роль пользователя.v-show, для частых переключений вроде табов, где важна скорость отрисовки без пересоздания узлов.
Обязательно ли использовать:key в v-for?
Формально
v-forработает без:key, но Vue будет выдавать предупреждение в консоли. Без ключа фреймворк не может отследить, какой элемент DOM соответствует какому элементу данных, это ведёт к багам при перестановках и удалении. Всегда указывайте:keyс уникальным идентификатором,idиз данных, а не индекс массива.
В чём разница между data() и computed?
data()хранит исходное состояние, «сырые» данные.computedпроизводит от них значение для отображения: форматированную строку, отфильтрованный массив, результат проверки. Вычисляемое свойство автоматически обновляется при изменении зависимостей и кешируется между рендерами. Методы же вызываются каждый раз заново и предназначены для действий, а не для отображения.
Можно ли миксовать Options API и Composition API?
Да, в одном проекте можно использовать оба стиля. Vue 3 поддерживает Options API полностью, а Composition API, это дополнительный инструмент для сложных сценариев. Для обучения мы рекомендуем начать с Options API: он структурированнее и ближе к ментальной модели «компонент = объект с настройками».
Что делать дальше: ваш следующий шаг во Vue 3
Вы написали первое приложение, разобрали директивы, вычисляемые свойства и компоненты. Это фундамент, на котором строятся любые проекты, от лендинга до панели администратора.
Дальше три пути. Первый, закрепить практикой: возьмите публичное API, например JSONPlaceholder, и отобразите список постов с фильтрацией через computed. Второй, перейти на Vite и .vue-файлы: официальная документация Vue 3 проведёт вас от create-vue до первого SFC за час. Третий, изучить Vue Router и Pinia для построения полноценного SPA.
Если хотите попробовать Vue вообще без установки, загляните в статью о подключении Vue через CDN. А стартовый код из этого руководства лежит в репозитории vue3-intro, форкните и экспериментируйте.



