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

14. Vue Router 4

Vue Router — официальный роутер Vue.js. Без него у тебя SPA без навигации — просто одна страница. С ним — полноценное многостраничное приложение с историей браузера, параметрами URL, вложенными маршрутами и ленивой загрузкой. Поехали! 🚀


Окно терминала
npm install vue-router@4
router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import HomeView from '@/views/HomeView.vue'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
// Ленивая загрузка! Чанк создаётся отдельно
component: () => import('@/views/AboutView.vue'),
},
{
path: '/users/:id',
name: 'user',
component: () => import('@/views/UserView.vue'),
},
{
// 404 - ловим всё остальное
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFoundView.vue'),
},
]
const router = createRouter({
// createWebHistory — чистые URL (требует настройки сервера)
// createWebHashHistory — URL с #hash (работает везде)
// createMemoryHistory — для SSR и тестов
history: createWebHistory(import.meta.env.BASE_URL),
routes,
})
export default router
main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App)
.use(router) // Регистрируем роутер
.mount('#app')

Два главных компонента роутера:

<!-- App.vue — корень приложения -->
<template>
<header>
<nav>
<!-- RouterLink — умная ссылка с активным классом -->
<RouterLink to="/">Главная</RouterLink>
<RouterLink to="/about">О нас</RouterLink>
<!-- По имени маршрута -->
<RouterLink :to="{ name: 'user', params: { id: 42 } }">
Мой профиль
</RouterLink>
<!-- С query параметрами -->
<RouterLink :to="{ path: '/search', query: { q: 'vue' } }">
Поиск
</RouterLink>
</nav>
</header>
<!-- Здесь отображается текущий маршрут -->
<RouterView />
</template>

Vue Router автоматически добавляет классы активным ссылкам:

<style>
/* router-link-active — ссылка частично совпадает с URL */
.router-link-active {
color: #42b883;
}
/* router-link-exact-active — точное совпадение */
.router-link-exact-active {
font-weight: bold;
border-bottom: 2px solid #42b883;
}
</style>
<!-- Кастомные классы активности -->
<RouterLink
to="/about"
active-class="my-active"
exact-active-class="my-exact-active"
>
О нас
</RouterLink>

// Роут с параметром
{ path: '/users/:id', component: UserView }
{ path: '/posts/:category/:slug', component: PostView }
// Опциональный параметр
{ path: '/users/:id?', component: UserView }
// Regex в параметре (только числа)
{ path: '/users/:id(\\d+)', component: UserView }
UserView.vue
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { computed } from 'vue'
const route = useRoute()
// route.params — текущие параметры маршрута
const userId = computed(() => Number(route.params.id))
const category = computed(() => route.params.category as string)
</script>
<template>
<h1>Пользователь #{{ userId }}</h1>
</template>
router/index.ts
{
path: '/users/:id',
component: UserView,
props: true, // route.params передаются как props!
}
<!-- UserView.vue — чище и тестируемее! -->
<script setup lang="ts">
// Просто пропс — компонент не знает о роутере
const props = defineProps<{
id: string
}>()
</script>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { computed } from 'vue'
const route = useRoute()
const router = useRouter()
// Чтение query параметров
const search = computed(() => route.query.q as string || '')
const page = computed(() => Number(route.query.page) || 1)
// Обновление query без перезагрузки страницы
function updateSearch(value: string) {
router.push({
query: {
...route.query, // Сохраняем остальные параметры
q: value,
page: 1, // Сбрасываем страницу при поиске
}
})
}
</script>

<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// push — добавляет запись в историю (можно вернуться)
function goToHome() {
router.push('/')
}
function goToUser(id: number) {
router.push({ name: 'user', params: { id } })
}
// replace — заменяет текущую запись (нельзя вернуться)
function redirectToLogin() {
router.replace({ name: 'login' })
}
// go — навигация по истории
function goBack() {
router.go(-1) // Назад
}
function goForward() {
router.go(1) // Вперёд
}
// push возвращает Promise!
async function saveAndGo() {
await saveData()
await router.push({ name: 'dashboard' })
console.log('Навигация завершена')
}
</script>

router/index.ts
const routes = [
{
path: '/users',
component: UsersLayout, // Содержит <RouterView>
children: [
{
path: '', // /users
name: 'users-list',
component: UsersList,
},
{
path: ':id', // /users/42
name: 'user-detail',
component: UserDetail,
children: [
{
path: 'posts', // /users/42/posts
component: UserPosts,
},
{
path: 'settings', // /users/42/settings
component: UserSettings,
},
],
},
],
},
]
<!-- UsersLayout.vue — содержит RouterView для дочерних маршрутов -->
<template>
<div class="users-layout">
<aside>
<RouterLink to="/users">Все пользователи</RouterLink>
</aside>
<main>
<!-- Здесь отображаются дочерние маршруты -->
<RouterView />
</main>
</div>
</template>

Всегда используй именованные маршруты вместо строк — это безопаснее при изменении путей:

// В роутере
{ path: '/users/:id/edit', name: 'user-edit', component: UserEdit }
<!-- В шаблоне -->
<!-- ❌ Хрупко — если изменишь path, всё сломается -->
<RouterLink to="/users/42/edit">Редактировать</RouterLink>
<!-- ✅ Надёжно — работает независимо от path -->
<RouterLink :to="{ name: 'user-edit', params: { id: 42 } }">
Редактировать
</RouterLink>

<script setup lang="ts">
import { RouterLink, useLink } from 'vue-router'
const props = defineProps({
...RouterLink.props,
})
const { route, href, isActive, isExactActive, navigate } = useLink(props)
</script>
<template>
<a
:href="href"
:class="{ 'is-active': isActive, 'is-exact': isExactActive }"
@click.prevent="navigate"
>
<slot />
</a>
</template>

// Создание роутера
createRouter({ history: createWebHistory(), routes })
// В компоненте
const router = useRouter() // Методы навигации
const route = useRoute() // Текущий маршрут (readonly)
// Навигация
router.push('/path')
router.push({ name: 'route-name', params: {}, query: {} })
router.replace({ name: 'login' })
router.go(-1)
// Информация о маршруте
route.path // '/users/42'
route.params // { id: '42' }
route.query // { search: 'vue' }
route.name // 'user'
route.meta // { requiresAuth: true }
route.fullPath // '/users/42?search=vue'