Vue Router
Vue Router 是 Vue.js 的官方路由管理器。它和 Vue.js 深度集成,让用 Vue.js 构建单页应用变得轻而易举。功能包括:
- 嵌套路由映射
- 动态路由选择
- 模块化、基于组件的路由配置
- 路由参数、查询、通配符
- 展示由 Vue.js 的过渡系统提供的过渡效果
- 细致的导航控制
- 自动激活 CSS 类的链接
- HTML5 history 模式或 hash 模式
- 可定制的滚动行为
- URL 的正确编码
安装
npm
bash
npm install vue-router@4
yarn
bash
yarn add vue-router@4
基本使用
1. 定义路由组件
vue
<!-- Home.vue -->
<template>
<div>
<h1>首页</h1>
<p>欢迎来到首页!</p>
</div>
</template>
<!-- About.vue -->
<template>
<div>
<h1>关于我们</h1>
<p>这是关于页面。</p>
</div>
</template>
2. 创建路由实例
js
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
3. 在应用中使用路由
js
// 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')
4. 在模板中使用路由
vue
<!-- App.vue -->
<template>
<div id="app">
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">关于</router-link>
</nav>
<router-view />
</div>
</template>
动态路由
路径参数
js
const routes = [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
在组件中获取参数:
vue
<template>
<div>
<h1>用户 {{ $route.params.id }}</h1>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id)
</script>
多个参数
js
const routes = [
{ path: '/user/:id/post/:postId', component: UserPost }
]
可选参数
js
const routes = [
// ? 表示可选参数
{ path: '/user/:id?', component: User }
]
重复参数
js
const routes = [
// + 表示一个或多个
{ path: '/user/:id+', component: User },
// * 表示零个或多个
{ path: '/user/:id*', component: User }
]
嵌套路由
js
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 会被渲染到 User 的 <router-view> 中
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 会被渲染到 User 的 <router-view> 中
path: 'posts',
component: UserPosts,
},
],
},
]
父组件模板:
vue
<!-- User.vue -->
<template>
<div class="user">
<h2>用户 {{ $route.params.id }}</h2>
<router-view />
</div>
</template>
编程式导航
router.push()
js
import { useRouter } from 'vue-router'
const router = useRouter()
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
router.replace()
js
// 替换当前位置,不会向 history 添加新记录
router.replace({ path: '/home' })
// 相当于
router.push({ path: '/home', replace: true })
router.go()
js
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
// 返回一条记录,与 router.back() 相同
router.go(-1)
// 前进 3 条记录
router.go(3)
// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
命名路由
js
const routes = [
{
path: '/user/:username',
name: 'user',
component: User
}
]
使用命名路由:
vue
<template>
<router-link :to="{ name: 'user', params: { username: 'erina' }}">
用户
</router-link>
</template>
命名视图
js
const routes = [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
]
模板中使用:
vue
<template>
<router-view class="view left-sidebar" name="LeftSidebar" />
<router-view class="view main-content" />
<router-view class="view right-sidebar" name="RightSidebar" />
</template>
路由守卫
全局前置守卫
js
router.beforeEach((to, from, next) => {
// 检查用户是否已登录
if (to.meta.requiresAuth && !isLoggedIn()) {
// 重定向到登录页面
next('/login')
} else {
next()
}
})
全局后置钩子
js
router.afterEach((to, from) => {
// 发送页面浏览统计
sendToAnalytics(to.fullPath)
})
路由独享守卫
js
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from, next) => {
// 只对这个路由生效
if (isValidUserId(to.params.id)) {
next()
} else {
next('/404')
}
}
}
]
组件内守卫
vue
<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
// 在渲染该组件的对应路由被验证前调用
onBeforeRouteEnter((to, from, next) => {
// 不能获取组件实例 `this` !
// 因为当守卫执行前,组件实例还没被创建
next()
})
// 在当前路由改变,但是该组件被复用时调用
onBeforeRouteUpdate((to, from, next) => {
// 举例来说,对于一个带有动态参数的路径 `/users/:id`
// 在 `/users/1` 和 `/users/2` 之间跳转的时候
// 由于会渲染同样的 `UserDetails` 组件,所以组件实例会被复用
// 而这个钩子就会在这个情况下被调用
next()
})
// 在导航离开渲染该组件的对应路由时调用
onBeforeRouteLeave((to, from, next) => {
// 通常用来预防用户在还未保存修改前突然离开
const answer = window.confirm('你确定要离开吗?你的修改还没有保存!')
if (answer) {
next()
} else {
next(false)
}
})
</script>
路由元信息
js
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true }
},
{
path: ':id',
component: PostsDetail,
// 任何人都可以阅读帖子
meta: { requiresAuth: false }
}
]
}
]
在守卫中使用:
js
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next()
}
})
懒加载
js
// 路由级别的代码分割
const routes = [
{
path: '/about',
name: 'About',
// 懒加载
component: () => import('../views/About.vue')
},
{
path: '/admin',
name: 'Admin',
// 将组件分组到同一个异步块中
component: () => import(/* webpackChunkName: "admin" */ '../views/Admin.vue')
}
]
滚动行为
js
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
// 如果有保存的位置(浏览器前进/后退)
if (savedPosition) {
return savedPosition
}
// 如果有锚点
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth'
}
}
// 默认滚动到顶部
return { top: 0 }
}
})
过渡效果
vue
<template>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</template>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
数据获取
导航完成后获取
vue
<template>
<div class="post">
<div v-if="loading" class="loading">加载中...</div>
<div v-if="error" class="error">{{ error }}</div>
<div v-if="post" class="content">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const post = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchData = async (id) => {
error.value = null
post.value = null
loading.value = true
try {
const response = await fetch(`/api/posts/${id}`)
post.value = await response.json()
} catch (err) {
error.value = err.toString()
} finally {
loading.value = false
}
}
// 监听路由参数变化
watch(
() => route.params.id,
(newId) => {
if (newId) {
fetchData(newId)
}
},
{ immediate: true }
)
</script>
导航完成前获取
vue
<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate } from 'vue-router'
const post = ref(null)
const error = ref(null)
const fetchData = async (id) => {
try {
const response = await fetch(`/api/posts/${id}`)
return await response.json()
} catch (err) {
throw new Error('获取数据失败')
}
}
onBeforeRouteEnter(async (to, from, next) => {
try {
const postData = await fetchData(to.params.id)
next(() => {
post.value = postData
})
} catch (err) {
next(false) // 取消导航
}
})
onBeforeRouteUpdate(async (to, from, next) => {
if (to.params.id !== from.params.id) {
try {
post.value = await fetchData(to.params.id)
next()
} catch (err) {
error.value = err.message
next(false)
}
} else {
next()
}
})
</script>
最佳实践
1. 路由结构组织
js
// 按功能模块组织路由
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/auth',
component: AuthLayout,
children: [
{ path: 'login', component: Login },
{ path: 'register', component: Register },
{ path: 'forgot-password', component: ForgotPassword }
]
},
{
path: '/dashboard',
component: DashboardLayout,
meta: { requiresAuth: true },
children: [
{ path: '', component: DashboardHome },
{ path: 'profile', component: Profile },
{ path: 'settings', component: Settings }
]
}
]
2. 路由守卫的使用
js
// 创建可复用的守卫函数
const requireAuth = (to, from, next) => {
if (store.getters.isAuthenticated) {
next()
} else {
next('/login')
}
}
const requireGuest = (to, from, next) => {
if (!store.getters.isAuthenticated) {
next()
} else {
next('/dashboard')
}
}
// 在路由中使用
const routes = [
{
path: '/login',
component: Login,
beforeEnter: requireGuest
},
{
path: '/dashboard',
component: Dashboard,
beforeEnter: requireAuth
}
]
3. 错误处理
js
// 全局错误处理
router.onError((error) => {
console.error('路由错误:', error)
// 发送错误到监控服务
if (error.type === 'ChunkLoadError') {
// 处理代码分割加载失败
window.location.reload()
}
})
// 404 处理
const routes = [
// ... 其他路由
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: NotFound
}
]
Vue Router 是构建 Vue.js 单页应用的核心工具,通过合理使用其功能,可以创建出用户体验良好、结构清晰的应用程序。