导航守卫 #
一、导航守卫概述 #
导航守卫用于通过跳转或取消的方式守卫导航,常用于权限控制、登录验证等场景。
1.1 守卫分类 #
text
全局守卫
├── beforeEach 全局前置守卫
├── beforeResolve 全局解析守卫
└── afterEach 全局后置守卫
路由独享守卫
├── beforeEnter 路由前置守卫
组件内守卫
├── beforeRouteEnter 进入前
├── beforeRouteUpdate 更新时
└── beforeRouteLeave 离开前
1.2 导航解析流程 #
text
1. 触发导航
2. 调用失活组件的beforeRouteLeave
3. 调用全局beforeEach
4. 重用的组件调用beforeRouteUpdate
5. 路由配置中调用beforeEnter
6. 解析异步路由组件
7. 激活组件中调用beforeRouteEnter
8. 调用全局beforeResolve
9. 导航确认
10. 调用全局afterEach
11. DOM更新后调用beforeRouteEnter的next回调
二、全局守卫 #
2.1 全局前置守卫 #
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
// to: 即将进入的目标路由
// from: 当前导航正要离开的路由
// next: 必须调用以resolve钩子
console.log('从', from.path, '到', to.path)
// 权限验证
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login')
} else {
next()
}
})
export default router
2.2 返回值替代next #
javascript
// Vue Router 4 推荐使用返回值
router.beforeEach((to, from) => {
// 返回false取消导航
if (!isAuthenticated()) {
return false
}
// 返回路由地址重定向
if (to.meta.requiresAuth && !isAuthenticated()) {
return '/login'
// 或
return { name: 'Login', query: { redirect: to.fullPath } }
}
// 返回undefined或true继续导航
})
2.3 全局解析守卫 #
javascript
// 在导航被确认之前,所有组件内守卫和异步路由组件被解析后调用
router.beforeResolve((to, from) => {
// 可以在这里进行最终检查
console.log('导航即将完成')
})
2.4 全局后置守卫 #
javascript
router.afterEach((to, from, failure) => {
// 导航完成后的操作
// 可用于分析、更新标题等
// 更新页面标题
document.title = to.meta.title || '默认标题'
// failure: 导航失败信息(如果导航失败)
if (failure) {
console.log('导航失败:', failure)
}
})
三、路由独享守卫 #
3.1 beforeEnter #
javascript
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from) => {
// 只有进入该路由时才触发
if (!isAdmin()) {
return '/403'
}
}
},
// 可以是函数数组
{
path: '/protected',
component: Protected,
beforeEnter: [checkAuth, checkPermission]
}
]
function checkAuth(to, from) {
if (!isAuthenticated()) {
return '/login'
}
}
function checkPermission(to, from) {
if (!hasPermission(to.meta.permission)) {
return '/403'
}
}
四、组件内守卫 #
4.1 组合式API中使用 #
vue
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 离开守卫
onBeforeRouteLeave((to, from) => {
const answer = window.confirm('确定要离开吗?未保存的数据将丢失。')
if (!answer) return false
})
// 更新守卫
onBeforeRouteUpdate((to, from) => {
// 路由参数变化时触发
// 如从 /user/1 到 /user/2
fetchUser(to.params.id)
})
</script>
4.2 选项式API中使用 #
vue
<script>
export default {
beforeRouteEnter(to, from, next) {
// 此时组件实例还未创建,无法访问this
// 可以通过回调访问组件实例
next(vm => {
vm.fetchData(to.params.id)
})
},
beforeRouteUpdate(to, from) {
// 路由参数变化时
this.fetchData(to.params.id)
},
beforeRouteLeave(to, from) {
// 离开时
if (this.hasUnsavedChanges) {
const answer = window.confirm('确定要离开吗?')
if (!answer) return false
}
}
}
</script>
五、实际应用示例 #
5.1 登录验证 #
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: Home
},
{
path: '/login',
component: Login,
meta: { guest: true }
},
{
path: '/dashboard',
component: Dashboard,
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 登录验证守卫
router.beforeEach((to, from) => {
const isAuthenticated = localStorage.getItem('token')
// 需要登录但未登录
if (to.meta.requiresAuth && !isAuthenticated) {
return {
path: '/login',
query: { redirect: to.fullPath }
}
}
// 已登录但访问登录页
if (to.meta.guest && isAuthenticated) {
return '/dashboard'
}
})
export default router
5.2 权限控制 #
javascript
// 权限检查函数
function checkPermission(requiredRoles) {
const userRoles = getUserRoles()
return requiredRoles.some(role => userRoles.includes(role))
}
// 路由配置
const routes = [
{
path: '/admin',
component: Admin,
meta: { roles: ['admin'] }
},
{
path: '/editor',
component: Editor,
meta: { roles: ['admin', 'editor'] }
}
]
// 权限守卫
router.beforeEach((to, from) => {
const requiredRoles = to.meta.roles
if (requiredRoles) {
if (!checkPermission(requiredRoles)) {
return '/403'
}
}
})
5.3 页面加载进度条 #
javascript
// 使用NProgress
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
router.beforeEach((to, from) => {
NProgress.start()
})
router.afterEach(() => {
NProgress.done()
})
5.4 页面标题 #
javascript
router.beforeEach((to, from) => {
// 设置页面标题
const title = to.meta.title
if (title) {
document.title = `${title} - 我的应用`
} else {
document.title = '我的应用'
}
})
5.5 防止重复导航 #
javascript
let lastPath = ''
router.beforeEach((to, from) => {
if (to.path === lastPath) {
return false
}
lastPath = to.path
})
5.6 数据预加载 #
javascript
// 路由配置
const routes = [
{
path: '/user/:id',
component: User,
meta: {
preload: (to) => fetchUser(to.params.id)
}
}
]
// 预加载守卫
router.beforeEach(async (to, from) => {
if (to.meta.preload) {
try {
to.meta.data = await to.meta.preload(to)
} catch (error) {
return '/error'
}
}
})
六、完整的守卫配置示例 #
javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue'),
meta: { title: '首页' }
},
{
path: '/login',
name: 'Login',
component: () => import('../views/Login.vue'),
meta: { title: '登录', guest: true }
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue'),
meta: { title: '控制台', requiresAuth: true }
},
{
path: '/admin',
name: 'Admin',
component: () => import('../views/Admin.vue'),
meta: { title: '管理后台', requiresAuth: true, roles: ['admin'] }
},
{
path: '/403',
name: 'Forbidden',
component: () => import('../views/403.vue'),
meta: { title: '无权限' }
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('../views/NotFound.vue'),
meta: { title: '页面未找到' }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 全局前置守卫
router.beforeEach((to, from) => {
// 开始进度条
NProgress.start()
// 设置页面标题
document.title = to.meta.title ? `${to.meta.title} - MyApp` : 'MyApp'
// 获取token
const token = localStorage.getItem('token')
// 需要登录的页面
if (to.meta.requiresAuth && !token) {
return {
path: '/login',
query: { redirect: to.fullPath }
}
}
// 已登录访问登录页
if (to.meta.guest && token) {
return '/dashboard'
}
// 权限检查
if (to.meta.roles) {
const userRoles = JSON.parse(localStorage.getItem('roles') || '[]')
const hasRole = to.meta.roles.some(role => userRoles.includes(role))
if (!hasRole) {
return '/403'
}
}
})
// 全局解析守卫
router.beforeResolve((to, from) => {
// 可以在这里进行最终检查
})
// 全局后置守卫
router.afterEach((to, from, failure) => {
// 结束进度条
NProgress.done()
// 滚动到顶部
window.scrollTo(0, 0)
// 记录导航日志
if (!failure) {
logNavigation(to, from)
}
})
export default router
七、导航失败处理 #
7.1 检测导航失败 #
javascript
import { isNavigationFailure, NavigationFailureType } from 'vue-router'
router.afterEach((to, from, failure) => {
if (isNavigationFailure(failure)) {
console.log('导航失败:', failure)
if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
console.log('导航被中止')
}
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
console.log('重复导航')
}
if (isNavigationFailure(failure, NavigationFailureType.cancelled)) {
console.log('导航被取消')
}
}
})
7.2 处理导航结果 #
vue
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
async function navigate() {
const result = await router.push('/dashboard')
if (result) {
console.log('导航失败:', result)
}
}
</script>
八、总结 #
守卫使用场景 #
| 守卫 | 使用场景 |
|---|---|
| beforeEach | 全局权限验证、登录检查 |
| beforeResolve | 最终检查、数据预加载 |
| afterEach | 更新标题、进度条结束 |
| beforeEnter | 路由级别权限控制 |
| beforeRouteLeave | 防止数据丢失、清理工作 |
| beforeRouteUpdate | 路由参数变化处理 |
最佳实践:
- 使用返回值替代next
- 合理组织守卫逻辑
- 避免在守卫中进行耗时操作
- 使用meta存储路由元信息
- 组合使用多种守卫实现复杂逻辑
最后更新:2026-03-26