全局解析守卫
Vue Router 全局解析守卫
一、什么是全局解析守卫?
全局解析守卫是 Vue Router 提供的三种全局守卫之一,通过 router.beforeResolve() 注册。它在导航被确认之前,所有组件内守卫和异步路由组件被解析之后触发。
三种全局守卫的执行时机对比
用户触发导航
↓
beforeEach (全局前置守卫)
↓
beforeEnter (路由独享守卫)
↓
beforeRouteUpdate (组件内守卫,复用组件时)
↓
解析异步路由组件
↓
beforeRouteEnter (组件内守卫,进入时)
↓
beforeResolve (全局解析守卫) ← 你在这里
↓
导航确认
↓
afterEach (全局后置钩子)
↓
组件生命周期开始 (created/mounted...)
二、什么时候触发?
1. 触发时机详解
全局解析守卫在两个关键事件完成后触发:
- ✅ 所有异步路由组件加载完成
- ✅ 所有
beforeRouteEnter组件内守卫执行完成
关键特性:此时路由组件已经解析完毕,但尚未渲染。
2. 代码示例
// router/index.js
import router from 'vue-router'
// 全局前置守卫
router.beforeEach((to, from, next) => {
console.log('1. beforeEach 执行')
next()
})
// 全局解析守卫
router.beforeResolve((to, from, next) => {
console.log('4. beforeResolve 执行')
console.log('此时组件已解析,但尚未创建实例')
next()
})
// 全局后置钩子
router.afterEach((to, from) => {
console.log('5. afterEach 执行')
})
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
console.log('3. beforeRouteEnter 执行')
next()
},
beforeRouteUpdate(to, from, next) {
console.log('组件复用时的守卫')
next()
}
}
三、应用场景
场景1:数据预加载的最佳位置
问题:在哪里加载数据最合适?
// ❌ 在组件 created 中加载
// 问题:用户可能看到未加载完成的空白页面
created() {
this.fetchData() // 页面已渲染,但数据还没到
}
// ✅ 在 beforeResolve 中加载
// 优势:数据加载完成后才进入页面
router.beforeResolve(async (to, from, next) => {
// 获取目标路由组件需要的数据
if (to.meta.requiresData) {
try {
// 可以访问到组件的静态选项
const component = to.matched[0].components.default
// 如果组件定义了预加载方法
if (component.asyncData) {
const data = await component.asyncData(to)
// 将数据存储到全局状态(如 Vuex)
store.commit('setPageData', data)
}
next()
} catch (error) {
next(error)
}
} else {
next()
}
})
<!-- 组件内定义预加载逻辑 -->
<script>
export default {
// 静态方法,在 beforeResolve 中调用
asyncData({ store, route }) {
return store.dispatch('fetchArticle', route.params.id)
},
mounted() {
// 此时数据已经存在,直接使用
console.log('页面数据已就绪')
}
}
</script>
场景2:权限验证的最终关卡
业务需求:某些页面需要异步验证权限,比如调用后端接口确认用户是否有访问权限。
router.beforeResolve(async (to, from, next) => {
// 需要动态权限验证的页面
if (to.meta.requiresDynamicPermission) {
// 确保用户信息已加载(可能来自 beforeEach 中的异步操作)
if (!store.state.user.userInfo) {
await store.dispatch('fetchUserInfo')
}
// 调用权限验证接口
const hasPermission = await checkUserPermission(to.path)
if (!hasPermission) {
next({
path: '/403',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next()
}
})
场景3:确保异步依赖就绪
业务需求:某些页面依赖特定的异步资源(如配置文件、第三方脚本等)。
router.beforeResolve(async (to, from, next) => {
// 检查是否需要加载配置
if (to.meta.requiresConfig && !window.appConfig) {
try {
// 加载配置文件
const config = await fetch('/config.json')
window.appConfig = config
// 动态加载依赖配置的模块
if (to.meta.lazyModule) {
await import(/* webpackChunkName: "lazy-module" */ './lazyModule')
}
next()
} catch (error) {
next(error)
}
} else {
next()
}
})
场景4:全局加载状态管理
业务需求:实现细粒度的加载状态控制。
// store/modules/loading.js
const loadingModule = {
state: {
isGlobalLoading: false,
loadingText: ''
},
mutations: {
SET_LOADING(state, { isLoading, text = '加载中...' }) {
state.isGlobalLoading = isLoading
state.loadingText = text
}
}
}
// router/index.js
let loadingTimeout = null
router.beforeEach((to, from, next) => {
// 设置超时保护,避免无限加载
loadingTimeout = setTimeout(() => {
store.commit('SET_LOADING', { isLoading: false })
}, 10000)
store.commit('SET_LOADING', {
isLoading: true,
text: `正在进入${to.meta.title || '页面'}...`
})
next()
})
router.beforeResolve((to, from, next) => {
// 在即将进入页面前,更新加载文案
store.commit('SET_LOADING', {
isLoading: true,
text: '即将进入页面...'
})
next()
})
router.afterEach(() => {
// 清除超时,关闭加载状态
clearTimeout(loadingTimeout)
store.commit('SET_LOADING', { isLoading: false })
})
场景5:埋点与数据统计
业务需求:需要精确记录用户进入页面的时机,包括异步数据加载完成的时间。
router.beforeResolve((to, from, next) => {
// 记录页面开始加载的时间点
if (!to.meta.startTime) {
to.meta.startTime = Date.now()
}
next()
})
router.afterEach((to, from) => {
// 上报页面加载性能数据
if (to.meta.startTime) {
const loadTime = Date.now() - to.meta.startTime
// 上报到监控平台
analytics.track('page_load_complete', {
path: to.path,
loadTime,
hasAsyncData: !!to.meta.requiresData
})
}
})
四、与 beforeEach 的区别
| 特性 | beforeEach | beforeResolve |
|---|---|---|
| 触发时机 | 导航开始时立即触发 | 异步组件解析后、导航确认前 |
| 能访问异步组件 | ❌ 不能 | ✅ 能 |
| 典型用途 | 基础鉴权、登录检查 | 数据预加载、确保依赖就绪 |
| 执行次数 | 每个导航至少一次 | 每个导航至少一次 |
| 可取消导航 | ✅ 可以 | ✅ 可以 |
五、注意事项和最佳实践
1. 避免在 beforeResolve 中访问组件实例
// ❌ 错误:此时组件实例还未创建
router.beforeResolve((to, from, next) => {
console.log(this.$route) // undefined
console.log(to.matched[0].instances.default) // undefined
next()
})
// ✅ 正确:通过组件选项访问
router.beforeResolve((to, from, next) => {
const component = to.matched[0].components.default
console.log(component.asyncData) // 可以访问静态方法
next()
})
2. 合理使用 next 函数
router.beforeResolve((to, from, next) => {
// ✅ 正确:确保所有分支都调用 next
if (condition) {
next('/login')
} else {
next() // 必须调用
}
// ❌ 错误:忘记调用 next
if (condition) {
next('/login')
}
// 条件不满足时没有 next,导航会卡住
})
3. 性能考虑
router.beforeResolve((to, from, next) => {
// ❌ 避免:每次导航都执行重操作
const heavyData = processLargeData()
// ✅ 优化:使用缓存
if (!cachedData) {
cachedData = processLargeData()
}
next()
})
六、总结
全局解析守卫的核心价值:
- 时机特殊:处于组件解析完成和导航确认之间的"黄金窗口期"
- 数据就绪:确保异步数据加载完成后才进入页面,提升用户体验
- 完整控制:能访问已解析的组件,进行最后的前置处理
- 应用广泛:适合数据预加载、权限验证、依赖管理、加载状态控制等场景
选择建议:
- 基础登录验证用
beforeEach - 需要等待异步组件加载的场景用
beforeResolve - 需要访问组件实例的操作用
beforeRouteEnter - 页面渲染后的操作用
afterEach
理解 beforeResolve 的触发时机和应用场景,能让你更精准地控制导航流程,写出更健壮的路由逻辑。
全局解析守卫
http://localhost:8090/archives/quan-ju-jie-xi-shou-wei