План по изучению Vue.js

Курс поможет освоить Vue.js с нуля: от основ реактивности и компонентов до маршрутизации, управления состоянием и сборки приложения. В конце вы сможете собрать полноценное SPA и подготовить проект к деплою.

1. Основы Vue: реактивность, шаблоны и директивы

Основы Vue: реактивность, шаблоны и директивы

Vue.js — это прогрессивный фреймворк для создания пользовательских интерфейсов. Его ключевая идея: вы описываете состояние (данные) и то, как интерфейс зависит от этого состояния, а Vue берёт на себя синхронизацию изменений данных с отображением.

В этой статье мы разберём базу, без которой дальше в Vue двигаться сложно:

  • что такое реактивность и как она работает в Vue 3
  • что такое шаблоны и что в них можно писать
  • что такое директивы и какие используются чаще всего
  • Официальная документация (рекомендуется держать открытой параллельно):

  • Введение в Vue
  • Основы реактивности
  • Синтаксис шаблонов
  • Встроенные директивы
  • Как Vue «оживляет» интерфейс

    Если очень упрощать, Vue делает следующее:

  • вы храните состояние в реактивных переменных
  • Vue отслеживает, какие части интерфейса зависят от каких данных
  • при изменении данных Vue обновляет только то, что реально должно измениться на странице
  • !Схема цикла: состояние → отображение → действия пользователя → новое состояние

    Реактивность во Vue

    Что такое реактивность

    Реактивность — это механизм, при котором изменения данных автоматически приводят к обновлению того, что от них зависит.

    В Vue 3 реактивность построена вокруг объектов Proxy (вам не нужно писать Proxy вручную), а в коде вы обычно пользуетесь двумя основными инструментами:

  • ref() для примитивов и единичных значений
  • reactive() для объектов (структурированных данных)
  • Справочник API:

  • Reactivity API: Core
  • ref()

    ref() создаёт реактивную «коробку» со значением. В JavaScript-коде значение лежит в поле .value.

    Что важно запомнить:

  • ref подходит для чисел, строк, булевых значений, а также для случаев, когда вам удобнее работать с единым значением
  • в обычном JS вы читаете и пишете через .value
  • в шаблонах (template) Vue обычно «разворачивает» ref автоматически, поэтому там .value чаще всего не пишут
  • reactive()

    reactive() делает реактивным объект (включая вложенные поля).

    Что важно запомнить:

  • reactive удобно для состояния «как в модели»: один объект со многими полями
  • если вам нужно легко заменять значение целиком (например, присвоить новый объект), часто удобнее хранить объект в ref
  • Производные значения: computed()

    Если вам нужно значение, которое вычисляется из других реактивных данных, используйте computed().

    Почему это лучше, чем «просто функция»:

  • Vue кеширует результат и пересчитывает его только при изменении зависимостей
  • зависимости определяются автоматически во время выполнения вычисления
  • Реакция на изменения: watch()

    watch() нужен, когда вы хотите выполнить побочный эффект в ответ на изменение данных: например, запрос к API, запись в localStorage, логирование.

    Правило на практике:

  • computed — для получения значения
  • watch — для запуска действий
  • Шаблоны Vue: что можно писать внутри

    Шаблон (template) — это декларативное описание интерфейса: как данные превращаются в отображение.

    Документация: Template Syntax

    Интерполяция

    Самая базовая форма — вывести значение в текст:

  • {{ message }}
  • Внутри фигурных скобок вы пишете JavaScript-выражение (не инструкцию), например:

  • {{ user.name }}
  • {{ isAdmin ? 'Админ' : 'Пользователь' }}
  • Практические ограничения (важно):

  • там должны быть выражения, а не полноценные блоки кода
  • сложную логику лучше переносить в computed или методы, чтобы шаблон оставался читаемым
  • Привязка атрибутов и свойств

    Чтобы связать значение с атрибутом, используется v-bind.

  • полный вид: v-bind:href="url"
  • сокращение: :href="url"
  • v-bind работает не только со строками: можно связывать числа, булевы значения, объекты для классов и стилей.

    Обработка событий

    Чтобы реагировать на события пользователя, используется v-on.

  • полный вид: v-on:click="increment"
  • сокращение: @click="increment"
  • Документация: Event Handling

    Директивы: управление DOM через декларативные правила

    Директива — это специальная конструкция вида v-..., которая говорит Vue, как применить данные к DOM.

    Полный список: Built-in Directives

    Ниже — самые важные.

    v-if, v-else-if, v-else и v-show

    Эти директивы отвечают за условное отображение.

    Документация:

  • Conditional Rendering
  • Отличие по смыслу:

  • v-if физически добавляет или убирает элемент из DOM
  • v-show оставляет элемент в DOM, но переключает видимость (обычно через CSS)
  • Как выбирать:

  • v-if — когда условие меняется редко или создание элемента дорого
  • v-show — когда нужно часто показывать и скрывать одно и то же
  • v-for и ключи key

    v-for нужен для вывода списков.

    Документация: List Rendering

    Типичная форма:

  • v-for="item in items"
  • Почти всегда вместе с v-for задают :key:

  • :key="item.id"
  • Зачем нужен key:

  • он помогает Vue правильно сопоставлять элементы списка при обновлениях
  • без корректного ключа можно получить странные визуальные эффекты и ошибки состояния вложенных компонентов
  • Практическое правило:

  • используйте стабильный уникальный идентификатор (например, id)
  • избегайте индекса массива как ключа, если список может меняться (вставки, удаления, сортировка)
  • v-model: двусторонняя связь ввода и состояния

    v-model связывает значение поля ввода с реактивным состоянием и автоматически обрабатывает ввод пользователя.

    Документация: Form Input Bindings

    Идея такая:

  • данные идут в поле ввода
  • пользователь меняет ввод
  • состояние обновляется автоматически
  • По сути v-model — это удобная комбинация :value="..." и @input="..." (для текстовых полей), но с нюансами для разных типов полей.

    Минимальный пример на Vue без шаблонов (через render)

    Иногда полезно увидеть, что Vue можно описывать и через render() — это помогает понять, что шаблоны являются удобной надстройкой.

    Что здесь важно в контексте темы:

  • count — реактивное состояние (ref)
  • doubled — производное значение (computed)
  • на клик меняется состояние, и Vue сам обновляет отображение
  • Типичные ошибки новичков

  • Пытаться хранить всё в одном огромном объекте без структуры и смысла
  • Злоупотреблять watch, когда достаточно computed
  • Делать сложную бизнес-логику прямо в шаблоне вместо методов и вычисляемых свойств
  • Использовать нестабильный key в v-for и потом долго ловить «странные баги»
  • Что дальше по курсу

    Дальше логично углубиться в следующие темы:

  • компоненты и их границы ответственности
  • props и события (взаимодействие компонентов)
  • жизненный цикл и хуки Composition API
  • работа с формами, валидация
  • маршрутизация и управление состоянием
  • 2. Компоненты: props, emit, слоты и жизненный цикл

    Компоненты: props, emit, слоты и жизненный цикл

    В прошлой статье мы разобрали реактивность, шаблоны и директивы: как состояние автоматически отражается в интерфейсе. Следующий шаг в изучении Vue — научиться разбивать интерфейс на компоненты и организовывать взаимодействие между ними.

    Компонент во Vue — это самостоятельная часть интерфейса со своим состоянием, логикой и публичным API для общения с родителем.

    Официальная документация (лучше держать рядом):

  • Основы компонентов
  • Props
  • События компонентов (emit)
  • Слоты
  • Хуки жизненного цикла
  • !Схема: данные идут вниз через props, события идут вверх через emit, а контент передаётся через slots

    Что такое компонент и зачем он нужен

    Компоненты решают сразу несколько практических задач:

  • уменьшают сложность: вместо одной большой страницы получается набор небольших блоков
  • повышают переиспользуемость: один компонент можно применять в разных местах
  • упрощают поддержку: меньше связей и проще тестировать
  • Важно договориться о границе ответственности:

  • компонент должен иметь понятный вход (props)
  • компонент должен иметь понятный выход (emit)
  • компонент должен уметь принимать вставляемый контент (slots), если это нужно
  • > Ментальная модель простая: props и slots приходят внутрь, emit выходит наружу.

    Минимальный компонент без шаблонов

    Обычно компоненты пишут в SFC (Single File Component), но для понимания механики полезно увидеть вариант на render().

    Что здесь происходит:

  • defineComponent(...) описывает компонент
  • setup() подготавливает состояние и функции
  • функция, которую возвращает setup, является render-функцией
  • h(...) создаёт виртуальные узлы, которые Vue превращает в DOM
  • Дальше мы будем развивать этот пример через props, emit и slots.

    Props: входные данные компонента

    Смысл props

    props — это данные, которые родитель передаёт в компонент.

    Ключевое правило:

  • propsтолько для чтения со стороны дочернего компонента
  • Почему так:

  • родитель владеет данными и контролирует их изменения
  • однонаправленный поток данных делает поведение предсказуемым
  • Объявление props

    Компонент должен явно объявлять props, которые он принимает.

    Практические замечания:

  • type нужен для валидации в режиме разработки и для читаемости API
  • default задаёт значение, если родитель ничего не передал
  • required: true сообщает, что prop обязателен
  • Почему нельзя менять props напрямую

    Если дочерний компонент изменит props.name, он изменит данные, которыми владеет родитель, и это ломает модель управления состоянием.

    Типичные корректные альтернативы:

  • завести локальное состояние на основе prop
  • попросить родителя изменить данные через emit
  • Emit: события из компонента наружу

    Зачем нужен emit

    emit — это способ сообщить родителю, что внутри компонента что-то произошло.

    Примеры событий:

  • пользователь нажал кнопку
  • форма стала валидной
  • нужно обновить значение в родителе
  • Объявление и вызов emit

    Во Vue 3 рекомендуется явно объявлять события через emits.

    Что важно:

  • emit('press', payload) отправляет событие press и данные payload
  • родитель подписывается на событие и решает, что делать дальше
  • Паттерн для изменения данных в родителе

    Один из самых частых сценариев: дочерний компонент просит обновить значение.

    Идея:

  • значение приходит через prop
  • изменение уходит через событие, например update:xxx
  • Даже если вы позже будете использовать v-model, под капотом концепция остаётся такой же: prop вниз, событие вверх.

    Slots: передача контента в компонент

    Что такое слот

    Слот — это место в дочернем компоненте, куда родитель может вставить свой контент.

    Зачем это нужно:

  • компоненты-обёртки: карточки, модальные окна, панели
  • переиспользуемая логика с разным содержимым
  • Во Vue есть:

  • слот по умолчанию
  • именованные слоты
  • scoped slots (слоты с данными от ребёнка к родителю)
  • Слот по умолчанию (default)

    В render-функции слоты доступны как объект slots.

    Здесь:

  • slots.default() возвращает массив виртуальных узлов
  • если родитель не передал контент, мы показываем запасной текст
  • Именованные слоты

    Именованные слоты нужны, когда у компонента несколько областей для контента, например заголовок и тело.

    Смысл:

  • slots.header() — контент именованного слота header
  • slots.default() — контент слота по умолчанию
  • Scoped slots: слот, которому ребёнок передаёт данные

    Иногда ребёнок хочет отдать в слот данные, чтобы родитель мог ими воспользоваться при рендеринге.

    Пример: компонент формирует элементы списка и отдаёт данные наружу, а родитель решает, как их отображать.

    Здесь:

  • slots.item — ожидаемый именованный слот item
  • slot({ item, index }) передаёт данные в слот
  • родитель может использовать item и index для формирования контента
  • Практическая мысль:

  • scoped slots — это способ построить расширяемый компонент без жёсткого шаблона внутри
  • Жизненный цикл компонента

    Жизненный цикл — это этапы существования компонента: создание, монтирование в DOM, обновления, размонтирование.

    Vue даёт хуки, чтобы безопасно подключать побочные эффекты и очищать их.

    Основные хуки

    Документация: Хуки жизненного цикла

    Чаще всего используются:

  • onMounted — когда компонент уже появился в DOM
  • onUpdated — после обновления DOM из-за изменений состояния
  • onUnmounted — перед уничтожением, удобно чистить ресурсы
  • А также:

  • onBeforeMount, onBeforeUpdate, onBeforeUnmount — если нужно «вклиниться» раньше
  • Пример: подписка и очистка

    Ключевая идея:

  • всё, что вы подключили (таймеры, подписки, обработчики событий), нужно отключать в onUnmounted
  • Мини-проект: компонент-счётчик с props, emit и slot

    Соберём вместе:

  • initial приходит через prop
  • при изменении счётчика компонент отправляет событие
  • подпись на кнопке приходит через слот
  • Разбор архитектуры:

  • initial используется только для инициализации локального count
  • сам prop initial мы не меняем
  • наружу мы сообщаем через emit('change', новоеЗначение)
  • слот по умолчанию получает данные { count }, чтобы родитель мог кастомизировать подпись
  • Типичные ошибки и хорошие практики

  • не меняйте props напрямую, вместо этого используйте локальное состояние или emit
  • не превращайте emit в «шину событий» для всего приложения, события должны быть про конкретный компонент
  • используйте слоты, когда компоненту нужна гибкость отображения, а не десятки props для каждой мелочи
  • обязательно чистите ресурсы в onUnmounted
  • Что дальше по курсу

    После того как вы уверенно чувствуете компоненты и их взаимодействие, логично переходить к:

  • маршрутизации (Vue Router)
  • управлению состоянием (например, Pinia)
  • работе с сервером и асинхронностью в компонентах
  • 3. Работа с данными: формы, запросы, композиции и хуки

    Работа с данными: формы, запросы, композиции и хуки

    В первых статьях курса мы разобрали, как Vue связывает состояние и интерфейс (реактивность, директивы), а затем научились строить интерфейс из компонентов и связывать их через props, emit, слоты и хуки жизненного цикла.

    Следующий практический уровень почти любого приложения:

  • ввод данных пользователем (формы)
  • проверка и подготовка данных (валидация, нормализация)
  • отправка и загрузка данных (HTTP-запросы)
  • вынесение повторяющейся логики в переиспользуемые композиции (composables)
  • корректное управление побочными эффектами через хуки жизненного цикла
  • Рекомендуемые ссылки из документации:

  • Form Input Bindings
  • Lifecycle Hooks
  • Composables
  • !Поток данных: форма → состояние → валидация → запрос → состояние запроса → UI

    Формы как управляемое состояние

    Главная идея форм во Vue: значение каждого поля должно жить в реактивном состоянии, а интерфейс должен быть просто отражением этого состояния.

    В прошлой статье мы уже использовали watch и хуки жизненного цикла. Здесь это пригодится для:

  • валидации при изменении полей
  • автоматической отправки или подгрузки данных
  • отмены запросов и очистки ресурсов при размонтировании
  • v-model как концепция

    В шаблонах v-model даёт удобную двустороннюю связь. Но важно понимать механику: это просто сочетание:

  • передачи значения в элемент ввода
  • подписки на событие изменения и записи нового значения в состояние
  • Так как в этом курсе мы часто показываем примеры через render() и h(), ниже — эквивалент v-model для текстового поля.

    Что здесь важно:

  • источник правдыname (реактивная переменная)
  • input всегда показывает name.value
  • любое изменение поля обновляет name.value
  • Хранение формы: reactive или несколько ref

    Оба подхода корректны:

  • несколько ref удобно для маленьких форм
  • reactive удобно для формы как цельного объекта
  • Пример с reactive:

    Практическая рекомендация:

  • если форма отправляется целиком, часто удобнее иметь один объект form
  • если поля живут независимо (особенно в разных компонентах), иногда проще держать отдельные ref
  • Валидация без магии: computed для ошибок

    Валидация обычно даёт два результата:

  • ошибки (что не так)
  • готовность к отправке (можно ли отправлять)
  • Хорошая базовая схема:

  • ошибки считаем через computed
  • кнопку отправки блокируем по isValid
  • ошибки показываем только после попытки отправки или после touched
  • Почему computed здесь удобно:

  • ошибки автоматически пересчитываются при изменении полей
  • не нужно вручную вызывать валидацию после каждого ввода
  • Отправка формы: состояние запроса и блокировки

    При отправке почти всегда нужны дополнительные состояния:

  • isSubmitting чтобы блокировать повторную отправку
  • serverError чтобы показывать ошибку сервера
  • Ниже пример, где форма отправляется, если валидна.

    Ключевые идеи:

  • валидация и UI разделены
  • мы всегда отменяем действие submit через e.preventDefault()
  • состояние отправки и ошибки — часть реактивного состояния
  • Запросы: загрузка, ошибки, отмена и гонки

    Vue не навязывает библиотеку для HTTP. Часто достаточно fetch. Главное — правильно организовать состояние запроса.

    Базовая модель состояния запроса

    Почти любой запрос удобно описать тремя реактивными значениями:

  • data результат
  • loading идёт ли запрос
  • error ошибка, если она была
  • fetch с проверкой ok

    Важно помнить: fetch не выбрасывает ошибку на HTTP 404/500. Ошибкой это станет только если вы сами проверите response.ok.

    Документация по fetch: Fetch API

    Отмена запросов через AbortController

    Отмена критична в двух случаях:

  • компонент размонтировался, а запрос ещё идёт
  • пользователь быстро меняет фильтры или строку поиска, и старые запросы должны быть отменены
  • Для этого используется AbortController.

    Документация: AbortController

    Composables: переиспользуемая логика через Composition API

    Composable — это функция (обычно с префиксом use), которая создаёт реактивное состояние и возвращает его наружу.

    Зачем они нужны:

  • выносить повторяющуюся логику (запросы, формы, таймеры)
  • уменьшать компоненты, оставляя им в основном рендер и связку событий
  • улучшать тестируемость: отдельно проверять логику
  • Пример: useFetchJson

    Ниже composable, который:

  • хранит data/loading/error
  • умеет отменять предыдущий запрос
  • предоставляет execute(url) для ручного запуска
  • Почему onUnmounted находится внутри composable:

  • composable владеет ресурсом (в данном случае активным запросом)
  • значит composable и должен его чистить
  • Правила использования composables

  • вызывайте composables синхронно внутри setup()
  • не вызывайте composables условно, чтобы порядок хуков был стабильным
  • возвращайте наружу только то, что нужно компоненту
  • Хуки и побочные эффекты: что важно в реальных приложениях

    Вы уже видели хуки жизненного цикла. В работе с данными они используются постоянно.

    Где запускать начальную загрузку

    Частый вариант:

  • запускать execute() в onMounted
  • Если вы загружаете данные на основе реактивных параметров (например, query), то часто запуск делается через watch.

    watch для запросов по изменению фильтров

    Типичная схема поиска:

  • query меняется
  • мы запускаем запрос
  • предыдущий запрос отменяется
  • Практическое замечание: для реального поиска обычно добавляют debounce, чтобы не отправлять запрос на каждый символ.

    Мини-паттерн: поле ввода как компонент с modelValue

    Когда формы становятся большими, удобно делать маленькие компоненты-поля. Во Vue стандартный контракт для v-model на компоненте:

  • вход: modelValue (prop)
  • выход: update:modelValue (emit)
  • Даже если вы не используете v-model в шаблоне, этот контракт остаётся полезным.

    Связь с предыдущей статьёй:

  • props описывают вход компонента
  • emit описывает выход
  • компонент не меняет props.modelValue напрямую
  • Итог: как собрать всё в одну устойчивую модель

    Если свести материал статьи к рабочему чек-листу:

  • состояние формы храните в ref/reactive
  • ошибки и производные состояния считайте через computed
  • для отправки добавляйте isSubmitting и обработку ошибок
  • для запросов держите data/loading/error
  • защищайтесь от гонок: отменяйте запросы через AbortController
  • повторяющуюся логику выносите в composables и очищайте ресурсы внутри них через onUnmounted
  • Дальше по курсу обычно логично добавить маршрутизацию и глобальное состояние (например, Pinia), чтобы строить приложения из нескольких экранов и управлять данными на уровне всего приложения.

    4. Vue Router: навигация, динамические маршруты и guards

    Vue Router: навигация, динамические маршруты и guards

    Большинство реальных приложений — это не один экран, а набор страниц: список, карточка, профиль, настройки. Во Vue за это отвечает роутер.

    Vue Router — официальный маршрутизатор для Vue 3. Он решает задачи:

  • сопоставление URL и компонента, который нужно показать
  • навигация между экранами без перезагрузки страницы
  • параметры в URL (например, id)
  • защита маршрутов (например, доступ только после входа)
  • Связь с предыдущими темами курса:

  • из статьи про компоненты вам уже знаком контракт props вниз, emit вверх — роутер добавляет ещё один источник данных для компонента: параметры маршрута
  • из статьи про работу с данными вы уже умеете делать запросы и отменять их — теперь научимся привязывать загрузку данных к маршрутам и переходам
  • Официальная документация:

  • Vue Router
  • Getting Started
  • Dynamic Route Matching
  • Navigation Guards
  • !Как роутер связывает URL и отображаемый компонент

    Установка и подключение роутера

    Обычно роутер ставят отдельно от Vue:

  • Установка Vue Router
  • Типичная схема интеграции:

  • создаём маршруты
  • создаём роутер
  • подключаем роутер в приложение через app.use(router)
  • показываем текущую страницу через RouterView
  • Базовая настройка: createRouter и createWebHistory

    Ниже пример в стиле курса без шаблонов, через render() и h().

    Что важно понять:

  • routes — это правила сопоставления path и компонента
  • createWebHistory() включает “нормальные” URL без #
  • RouterLink создаёт ссылки и делает переход без перезагрузки
  • RouterView — место, где рендерится компонент текущего маршрута
  • Маршруты и имена: зачем нужен name

    Маршрут можно называть:

    Почему это полезно:

  • проще навигировать по имени, не завися от строкового пути
  • проще поддерживать большие приложения, где пути могут меняться
  • Пример навигации по имени:

    Динамические маршруты: параметры в URL

    Динамический маршрут — это путь с параметром, например:

  • /users/42
  • Описывается так:

    Здесь :idпараметр маршрута.

    Чтение параметров: useRoute

    Внутри компонента текущий маршрут доступен через useRoute().

    Практические замечания:

  • значения route.params обычно строки, даже если в URL число
  • если вам нужно число, приводите тип явно: Number(route.params.id) и проверяйте NaN
  • Параметры запроса: query

    URL может быть таким:

  • /search?q=vue&page=2
  • Тогда параметры лежат в route.query.

    Различайте:

  • params — часть пути (обычно обязательная для совпадения маршрута)
  • query — строка запроса (обычно опционально)
  • Программная навигация: useRouter

    Если нужно перейти при событии, используйте useRouter().

    Полезные факты:

  • router.push(...) добавляет запись в историю браузера
  • router.replace(...) заменяет текущую запись (например, после логина)
  • Привязка данных к маршруту: загрузка по params и отмена запросов

    Типичный сценарий:

  • вы открываете /users/:id
  • компонент должен загрузить пользователя по id
  • при смене id без размонтирования компонента нужно перезагрузить данные
  • при быстром переключении важно отменять предыдущие запросы
  • Это напрямую связывает роутер с предыдущей статьёй про watch, AbortController и composables.

    Ниже пример, который использует composable useFetchJson из прошлой статьи и реагирует на смену id.

    Почему watch(..., { immediate: true }) здесь уместен:

  • он запускает загрузку при первом рендере
  • он перезапускает загрузку при изменении id
  • composable внутри себя отменяет прошлый запрос, защищая от гонок
  • Вложенные маршруты: структура страниц

    Вложенные маршруты помогают, когда у страницы есть общий “каркас”, а внутри него меняется секция.

    Например:

  • /settings/profile
  • /settings/security
  • Схема:

  • родительский маршрут рендерит страницу настроек
  • внутри неё есть второй RouterView для дочерних секций
  • Ключевая идея:

  • один RouterView соответствует одному “уровню” маршрутов
  • Подробности: Nested Routes

    Guards: контроль доступа и логика перед переходом

    Navigation guards — это функции, которые запускаются при попытке перейти на маршрут.

    Они нужны для:

  • защиты маршрутов (нужна авторизация)
  • предотвращения ухода со страницы с несохранённой формой
  • редиректов (например, после логина)
  • аналитики переходов
  • Документация: Navigation Guards

    Глобальный guard: router.beforeEach

    Глобальный guard срабатывает на каждый переход.

    Пример: закрытые страницы требуют авторизации.

    Что здесь происходит:

  • meta.requiresAuth — ваш собственный флаг на маршруте
  • to — маршрут, куда пытаемся перейти
  • если доступ запрещён, guard возвращает объект редиректа
  • Практическая рекомендация:

  • не храните реальную авторизацию только в localStorage; в учебных примерах это допустимо, но в приложении лучше иметь нормальное состояние авторизации и обновление токена
  • Guard на конкретном маршруте: beforeEnter

    Если проверка нужна только для одного маршрута или группы, используйте beforeEnter.

    Смысл:

  • логика находится рядом с маршрутом
  • глобальный guard не усложняется
  • Guards на уровне компонента: уход со страницы и смена параметров

    Когда логика зависит от состояния компонента (например, форма “грязная”), удобнее ставить guard прямо в компоненте.

    Во Vue Router для Composition API используются функции:

  • onBeforeRouteLeave — перед уходом с текущего маршрута
  • onBeforeRouteUpdate — когда маршрут обновился, но компонент переиспользуется (часто при смене params)
  • Документация: In-Component Guards

    Пример: спрашиваем подтверждение, если есть несохранённые изменения.

    Важно:

  • guard возвращает true, чтобы разрешить переход
  • guard возвращает false, чтобы запретить
  • guard может вернуть редирект-объект
  • Частые ошибки при работе с роутером

  • Пытаться “пробросить” всё через props, игнорируя route.params и route.query как источник данных.
  • Забывать, что params обычно строки, и получать неожиданные сравнения и запросы.
  • Делать тяжёлую логику в глобальном beforeEach, превращая его в узкое место.
  • Не отменять запросы при быстрой смене параметров, получая гонки и “скачущий” интерфейс.
  • Что дальше

    После роутинга обычно добавляют глобальное управление состоянием и авторизацией:

  • Pinia для хранения пользователя, токена, настроек
  • связка: guards проверяют состояние из Pinia, а страницы выполняют загрузку данных по маршруту
  • Эта связка превращает набор компонентов в цельное приложение с навигацией, безопасностью и устойчивой загрузкой данных.

    5. Управление состоянием и продакшен: Pinia, сборка, тесты, деплой

    Управление состоянием и продакшен: Pinia, сборка, тесты, деплой

    После роутинга обычно наступает момент, когда приложение перестаёт быть набором изолированных компонентов и превращается в систему:

  • несколько страниц делят общие данные (пользователь, токен, настройки, корзина)
  • guards во Vue Router должны проверять авторизацию
  • загрузки нужно переиспользовать, кешировать и корректно сбрасывать
  • проект нужно собирать, тестировать и деплоить так, чтобы он работал стабильно в продакшене
  • В этой статье разберём практический набор: Pinia для глобального состояния, Vite для сборки, Vitest и Vue Test Utils для тестов, и типовой деплой SPA.

    Рекомендуемая документация:

  • Pinia
  • Vite
  • Vitest
  • Vue Test Utils
  • Vue Devtools
  • !Общая картина, где Pinia находится между UI, роутером и запросами

    Зачем вообще нужно управление состоянием

    В прошлых статьях у вас уже есть инструменты:

  • локальное состояние через ref() и reactive()
  • переиспользуемая логика через composables
  • загрузки через fetch, watch, отмену запросов
  • навигация и guards через Vue Router
  • Проблема возникает, когда одни и те же данные нужны в разных местах:

  • шапка показывает пользователя, а страница профиля редактирует его
  • guards должны знать, залогинен ли пользователь
  • разные страницы используют общие справочники или настройки
  • Тогда появляется глобальное состояние.

    Когда достаточно локального состояния, а когда нужен store

    | Ситуация | Лучше локально (ref/reactive) | Лучше глобально (Pinia) | |---|---|---| | Состояние нужно только внутри одного компонента | Да | Нет | | Состояние делят 2-3 компонента в одном дереве | Часто да (через props/emit) | Иногда | | Состояние нужно на разных страницах и уровнях | Нет | Да | | Нужны кэширование, общий доступ, сброс по выходу | Сложно | Да | | Нужно, чтобы роутер и guards читали данные | Неудобно | Да |

    Практическое правило: если вы начинаете пробрасывать одно и то же через много уровней props, пора рассмотреть Pinia.

    Pinia как стандарт для Vue 3

    Pinia — официальный рекомендуемый стор для Vue. Он хорошо дружит с Composition API и Devtools.

    Ключевые понятия:

  • store — модуль состояния (например, auth, cart, settings)
  • state — данные стора
  • getters — производные значения, аналог computed
  • actions — методы, которые меняют state (включая async)
  • Установка и подключение Pinia

    Установка:

  • Установка Pinia
  • Подключение в приложении:

    Что важно:

  • Pinia подключается один раз на уровень приложения
  • после этого любой компонент может импортировать и использовать store
  • Создание store: вариант с Composition API внутри (setup store)

    Этот стиль хорошо сочетается с тем, что вы уже делали в composables.

    Пример: auth store.

    Почему это удобно:

  • getters описываются как computed
  • actions описываются как обычные функции
  • state можно группировать как в composable
  • Использование store в компонентах

    Store подключается через вызов функции use...Store().

    Практическое правило архитектуры:

  • компоненты читают состояние и вызывают actions
  • сложную логику загрузок и преобразований старайтесь держать в actions и composables
  • Связка Pinia и Vue Router: guards через store

    В статье про Vue Router вы делали guards на основе localStorage. Теперь лучше читать состояние из Pinia.

    Пример: защищаем маршруты с meta.requiresAuth.

    Что важно:

  • guard остаётся простым: он только решает, можно ли перейти
  • реальная логика авторизации живёт в auth store
  • Как организовать store в реальном проекте

    Делите по доменам

    Частые домены:

  • auth (токен, пользователь)
  • settings (язык, тема)
  • cart (корзина)
  • entities или cache (кэш справочников)
  • Не храните в store всё подряд

    Pinia не заменяет:

  • локальное состояние полей формы
  • одноразовое состояние модалки
  • Store нужен, когда данные имеют смысл на уровне приложения.

    Сброс состояния при выходе

    Когда пользователь выходит, часто нужно очистить несколько stores.

    Подходы:

  • у каждого store сделать action reset()
  • в auth.logout() вызвать сброс других stores
  • Сборка проекта для продакшена (Vite)

    Если вы создавали проект через create-vue, скорее всего у вас уже Vite.

    Документация:

  • Build в Vite
  • Типовые команды:

    Смысл команд:

  • dev запускает dev-сервер
  • build собирает оптимизированный бандл в папку dist
  • preview локально запускает сервер, который отдаёт именно dist (это полезно, чтобы поймать проблемы до деплоя)
  • Переменные окружения

    В Vite переменные, доступные в клиентском коде, обычно должны начинаться с префикса VITE_.

    Практика:

  • секреты не кладите в клиентские переменные окружения, потому что бандл можно прочитать
  • в клиенте держите только публичные конфиги (например, base URL)
  • Ленивая загрузка страниц

    Вы уже видели () => import('./pages/...'). Это даёт разделение кода по чанкам.

    Почему это важно:

  • стартовый бандл меньше
  • приложение быстрее начинает работать
  • Тесты: что и как тестировать

    В продакшене тесты нужны не ради галочки, а чтобы уверенно менять код.

    Полезная схема уровней:

  • unit-тесты: тестируем чистую логику (например, actions/getters в store)
  • component-тесты: тестируем компонент в изоляции (рендер, события)
  • e2e-тесты: тестируем сценарии пользователя в браузере
  • Unit-тесты и component-тесты (Vitest + Vue Test Utils)

    Документация:

  • Vitest
  • Vue Test Utils
  • Тестирование Pinia store

    Пример unit-теста, который проверяет, что login() выставляет токен.

    Что здесь происходит:

  • setActivePinia(createPinia()) создаёт изолированную Pinia для каждого теста
  • тест не зависит от реального приложения
  • Тестирование компонента с Pinia

    Для компонент-тестов часто используют подход с тестовой Pinia.

    Документация:

  • Testing Pinia
  • Практический смысл stubActions:

  • actions не выполняют реальную логику (и не делают запросы)
  • вы тестируете UI-реакцию, а не сеть
  • E2E-тесты (Cypress или Playwright)

    Если вам нужно проверить сценарии уровня пользователь открыл страницу, ввёл данные, перешёл по ссылке, берут e2e.

    Документация:

  • Cypress
  • Playwright
  • Практика для начала:

  • покройте 2-3 критичных сценария: логин, навигация по защищённым страницам, отправка ключевой формы
  • Деплой SPA: что важно не забыть

    После npm run build у вас появляется папка dist. Её обычно и деплоят.

    SPA fallback

    Ключевая проблема SPA:

  • при прямом открытии /profile сервер должен вернуть index.html, чтобы роутинг обработался на клиенте
  • На хостингах это настраивается как rewrite или fallback to index.html.

    Документация популярных платформ:

  • Netlify Redirects
  • Vercel
  • GitHub Pages
  • Базовый чек-лист перед релизом

  • запуск npm run build без ошибок
  • проверка npm run preview
  • проверка, что env переменные заданы на платформе деплоя
  • проверка SPA fallback
  • проверка, что guards и Pinia не зависят от случайного состояния в localStorage
  • Итог: как связать всё из курса в устойчивое приложение

    На этом этапе у вас складывается целостная архитектура:

  • компоненты управляют UI и событиями
  • composables выносят переиспользуемую логику
  • Pinia хранит глобальное состояние и предоставляет actions
  • Vue Router управляет страницами и guards
  • Vite собирает проект для продакшена
  • Vitest и Vue Test Utils дают быстрые тесты логики и компонентов
  • деплой требует правильной настройки SPA fallback
  • Если вы закрепите это небольшим проектом, вы фактически пройдёте путь от учебных компонентов к продакшен-практикам Vue-приложений.