Установка и конфигурация роутера, динамические маршруты, навигационные guards, вложенные маршруты, lazy loading
Vue Router — официальная библиотека маршрутизации для Vue.js. Версия 4 разработана специально для Vue 3 и полностью интегрирована с Composition API.
Vue Router устанавливается как отдельный пакет и регистрируется как плагин в приложении Vue.
Установка через npm:
npm install vue-router@4Базовая конфигурация создаётся в отдельном файле. Рекомендуется держать конфигурацию роутера отдельно от точки входа:
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
import AboutView from '@/views/AboutView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: AboutView
}
]
const router = createRouter({
// createWebHistory — HTML5 History API, чистые URL без #
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
export default routerРегистрируем роутер в приложении через app.use():
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')Vue Router предоставляет несколько режимов истории. Выбор зависит от возможностей сервера.
createWebHistory использует HTML5 History API. URL выглядят чисто: /about, /users/123. Требует настройки сервера — все запросы должны перенаправляться на index.html:
// Чистые URL: http://example.com/about
import { createWebHistory } from 'vue-router'
const router = createRouter({ history: createWebHistory() })createWebHashHistory добавляет # в URL: /#/about, /#/users/123. Работает без настройки сервера, так как всё после # не отправляется на сервер. Подходит для статического хостинга:
// URL с хешем: http://example.com/#/about
import { createWebHashHistory } from 'vue-router'
const router = createRouter({ history: createWebHashHistory() })createMemoryHistory хранит историю в памяти — используется для SSR и тестов, не изменяет URL браузера.
<RouterView> — точка монтирования: здесь рендерится компонент, соответствующий текущему маршруту. <RouterLink> — замена тега <a>, умеет определять активный маршрут:
<!-- App.vue -->
<template>
<div id="app">
<nav>
<!-- RouterLink не перезагружает страницу, обновляет историю -->
<RouterLink to="/">Главная</RouterLink>
<RouterLink to="/about">О нас</RouterLink>
<!-- С именованным маршрутом -->
<RouterLink :to="{ name: 'user', params: { id: 123 } }">
Профиль
</RouterLink>
<!-- Кастомный активный класс -->
<RouterLink
to="/admin"
active-class="nav-active"
exact-active-class="nav-exact-active"
>
Администрация
</RouterLink>
</nav>
<!-- Компонент текущего маршрута рендерится здесь -->
<RouterView />
</div>
</template>Vue Router автоматически добавляет классы router-link-active (когда маршрут активен или является родительским) и router-link-exact-active (точное совпадение). Это удобно для стилизации навигации.
Динамические сегменты позволяют создавать маршруты с переменными частями URL, например /users/:id:
// src/router/index.js
const routes = [
{
path: '/users/:id',
name: 'user',
component: () => import('@/views/UserView.vue')
},
{
// Несколько динамических сегментов
path: '/posts/:year/:month/:slug',
name: 'post',
component: () => import('@/views/PostView.vue')
},
{
// Опциональный параметр
path: '/search/:query?',
name: 'search',
component: () => import('@/views/SearchView.vue')
}
]Доступ к параметрам в компоненте через useRoute():
<!-- UserView.vue -->
<template>
<div>
<div v-if="isLoading">Загрузка...</div>
<div v-else-if="user">
<h1>{{ user.name }}</h1>
<p>Email: {{ user.email }}</p>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const user = ref(null)
const isLoading = ref(false)
// Загружаем данные при изменении параметра
watch(
() => route.params.id,
async (newId) => {
isLoading.value = true
const res = await fetch(`/api/users/${newId}`)
user.value = await res.json()
isLoading.value = false
},
{ immediate: true }
)
</script>Также можно передавать params как props компонента — это делает компонент независимым от роутера:
// router/index.js
{
path: '/users/:id',
component: UserView,
props: true // route.params автоматически становятся props
}useRouter() предоставляет экземпляр роутера для программной навигации:
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// Навигация к маршруту — добавляет запись в историю
async function login(credentials) {
await authApi.login(credentials)
// По имени маршрута
router.push({ name: 'dashboard' })
}
// replace — заменяет текущую запись истории (кнопка «назад» не вернёт)
function redirectToLogin() {
router.replace({ name: 'login', query: { redirect: route.fullPath } })
}
// go() — перемещение по истории
function goBack() {
router.go(-1) // То же что router.back()
}
// С параметрами и query
function navigateToUser(userId) {
router.push({
name: 'user',
params: { id: userId },
query: { tab: 'profile' },
hash: '#contacts'
})
}
</script>Query-параметры и хэш — дополнительные части URL для фильтрации, состояния UI:
<template>
<!-- URL: /products?category=electronics&sort=price&page=2 -->
<div>
<select v-model="category" @change="updateQuery">
<option value="">Все категории</option>
<option value="electronics">Электроника</option>
<option value="clothing">Одежда</option>
</select>
<select v-model="sort" @change="updateQuery">
<option value="date">По дате</option>
<option value="price">По цене</option>
</select>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
// Инициализируем из query params
const category = ref(route.query.category || '')
const sort = ref(route.query.sort || 'date')
// Синхронизируем URL с состоянием
function updateQuery() {
router.push({
query: {
category: category.value || undefined, // undefined удаляет параметр
sort: sort.value,
page: 1 // Сбрасываем пагинацию
}
})
}
// Реагируем на изменение URL (например, кнопка "назад")
watch(
() => route.query,
(newQuery) => {
category.value = newQuery.category || ''
sort.value = newQuery.sort || 'date'
}
)
</script>Вложенные маршруты позволяют создавать иерархические интерфейсы с несколькими уровнями <RouterView>:
// router/index.js
const routes = [
{
path: '/dashboard',
component: DashboardLayout,
// children будут рендериться внутри RouterView в DashboardLayout
children: [
{
path: '', // /dashboard
name: 'dashboard',
component: DashboardHome
},
{
path: 'analytics', // /dashboard/analytics
name: 'dashboard-analytics',
component: DashboardAnalytics
},
{
path: 'settings', // /dashboard/settings
component: DashboardSettings,
children: [
{
path: 'profile', // /dashboard/settings/profile
component: SettingsProfile
},
{
path: 'security', // /dashboard/settings/security
component: SettingsSecurity
}
]
}
]
}
]<!-- DashboardLayout.vue — содержит второй RouterView -->
<template>
<div class="dashboard">
<aside>
<RouterLink :to="{ name: 'dashboard' }">Главная</RouterLink>
<RouterLink :to="{ name: 'dashboard-analytics' }">Аналитика</RouterLink>
<RouterLink to="/dashboard/settings/profile">Профиль</RouterLink>
</aside>
<main>
<!-- Дочерний компонент рендерится здесь -->
<RouterView />
</main>
</div>
</template>Guards — это хуки, которые позволяют контролировать навигацию: аутентификация, проверка прав, несохранённые изменения:
// router/index.js — глобальные guards
// beforeEach выполняется перед каждым переходом
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
// Маршруты требующие авторизации помечаем meta.requiresAuth
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
// Сохраняем куда хотел перейти пользователь
next({ name: 'login', query: { redirect: to.fullPath } })
return
}
// Маршруты только для гостей (login/register)
if (to.meta.guestOnly && authStore.isLoggedIn) {
next({ name: 'dashboard' })
return
}
// Продолжаем навигацию
next()
})
// afterEach — выполняется после перехода (нет next)
router.afterEach((to, from) => {
// Обновляем заголовок страницы
document.title = to.meta.title ?? 'My App'
// Аналитика
analytics.trackPageView(to.path)
})Маршруты с мета-данными:
const routes = [
{
path: '/profile',
name: 'profile',
component: ProfileView,
meta: { requiresAuth: true, title: 'Профиль' }
},
{
path: '/login',
name: 'login',
component: LoginView,
meta: { guestOnly: true, title: 'Вход' }
}
]Внутрикомпонентные guards через Composition API:
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
const hasUnsavedChanges = ref(false)
// Вызывается перед уходом с этого компонента
onBeforeRouteLeave((to, from, next) => {
if (hasUnsavedChanges.value) {
const confirmed = window.confirm('У вас есть несохранённые изменения. Покинуть страницу?')
next(confirmed)
} else {
next()
}
})
// Вызывается когда маршрут меняется, но компонент остаётся (другой параметр)
onBeforeRouteUpdate(async (to) => {
// Например, переход /users/1 -> /users/2
await loadUser(to.params.id)
})
</script>Ленивая загрузка разделяет бандл на чанки — компоненты загружаются только когда пользователь переходит к соответствующему маршруту:
// Все компоненты загружаются лениво через динамический импорт
const routes = [
{
path: '/',
// Критический путь — можно грузить сразу
component: HomeView
},
{
path: '/admin',
// Большой admin-бандл — грузим лениво
component: () => import('@/views/AdminView.vue')
},
{
path: '/charts',
// Группировка в именованный чанк
component: () => import(/* webpackChunkName: "charts" */ '@/views/ChartsView.vue')
}
]Для Vite (который используется по умолчанию в Vue 3) именование чанков делается иначе:
// Vite: используем rollupOptions в vite.config.js для именования
// Или просто используем динамический импорт — Vite сам управляет чанками
component: () => import('@/views/AnalyticsView.vue')Соберём всё вместе — конфигурация роутера для реального приложения с авторизацией:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
redirect: '/dashboard'
},
{
path: '/auth',
component: () => import('@/layouts/AuthLayout.vue'),
meta: { guestOnly: true },
children: [
{ path: 'login', name: 'login', component: () => import('@/views/LoginView.vue') },
{ path: 'register', name: 'register', component: () => import('@/views/RegisterView.vue') }
]
},
{
path: '/dashboard',
component: () => import('@/layouts/DashboardLayout.vue'),
meta: { requiresAuth: true },
children: [
{ path: '', name: 'dashboard', component: () => import('@/views/DashboardView.vue') },
{ path: 'profile', name: 'profile', component: () => import('@/views/ProfileView.vue') },
{ path: 'settings', name: 'settings', component: () => import('@/views/SettingsView.vue') }
]
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFoundView.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// Восстанавливаем позицию прокрутки при navigating назад
if (savedPosition) return savedPosition
if (to.hash) return { el: to.hash, behavior: 'smooth' }
return { top: 0 }
}
})
export default routerVue Router 4 предоставляет все необходимые инструменты для навигации в SPA: декларативное определение маршрутов, динамические сегменты, вложенные маршруты для сложных лейаутов, guards для контроля доступа, ленивая загрузка для оптимизации производительности. Хуки useRoute() и useRouter() органично вписываются в Composition API.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.