4.14.5 导航守卫
总共7个守卫,3个全局守卫:全局前置守卫beforeEach
、路由前置解析守卫beforeResolve
、全局后置守卫afterEach
,1个路由独享守卫:进入任一路由前守卫beforeEnter
,3个组件内导航守卫钩子:进入新目标前守卫beforeRouteEnter
、路由更新前守卫beforeRouteUpdate
、离开当前实例前守卫beforeRouteLeave
4.14.5.1 完整的导航解析流程——守卫触发时机
具体的Vue router导航解析流程如下图所示,
- 导航被触发:用户点击链接或使用编程式导航触发路由变化。
- 在失活的组件里调用路由离开当前实例前守卫
beforeRouteLeave
。 - 调用全局的进入任一路由前守卫
beforeEach
。 - 在重用的组件里调用路由更新前守卫(vue2.2+)
beforeRouteUpdate
。 - 在路由配置里调用进入不同路由前守卫
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用进入新的目标实例前守卫
beforeRouteEnter
。 - 调用全局的解析任一路由前守卫(vue2.5+)
beforeResolve
。 - 导航被确认。
- 调用全局的路由钩子
afterEach
。 - 触发DOM更新。
- 调用进入新目标前守卫
beforeRouteEnter
中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
该流程图展示了从用户触发导航开始,到最终渲染组件的整个流程。以下是一些关键步骤的简要说明:
- 用户触发导航:用户点击链接或使用编程式导航触发路由变化。
- Vue Router 接收导航事件:Vue Router 监听到导航事件,并开始处理。
- 查找匹配的路由记录:Vue Router 查找配置中与目标路径匹配的路由记录。
-
检查路由守卫:如果找到匹配的路由,Vue Router 会检查路由守卫(
beforeEnter
、beforeEach
)。 -
执行路由组件的生命周期钩子:在路由守卫通过后,Vue Router 会执行组件的导航守卫钩子(
beforeRouteEnter
、beforeRouteUpdate
、beforeRouteLeave
)。 -
执行全局守卫:Vue Router 还会执行全局前置守卫(
beforeEach
)和解析守卫(beforeResolve
),以及全局后置钩子(afterEach
)。 - 渲染组件:所有守卫都通过后,Vue Router 会渲染对应的组件。
请注意,这个流程图是一个简化的版本,实际的 Vue Router 导航解析流程可能更复杂,涉及到更多的细节和配置选项。
概念理解:
失活组件,即将转换到后台不直接面向用户的组件(页面);
被激活组件,即将转换到前台而直接页面向用户的组件(页面);
重用的组件,在Vue中,"重用的组件"通常指的是在多个不同的路由视图中被复用的组件。Vue 2.2+版本引入了beforeRouteUpdate
这个路由守卫,它允许开发者在路由参数发生变化时,重新渲染当前组件之前执行一些逻辑。
具体来说,当你使用Vue Router时,每个路由可以关联一个或多个组件。如果这些组件在不同的路由中被复用,那么它们就被称为“重用的组件”。例如,你可能有一个用户信息组件,它在多个页面上显示用户信息,而这些页面可能对应不同的路由。
beforeRouteUpdate
守卫是在路由对象上定义的,它在两个条件满足时触发:
- 路由的路径保持不变,但是查询参数或hash发生变化。
- 路由的路径发生变化,但是当前组件被重用(即组件实例保持活动状态)。
在beforeRouteUpdate
中,你可以访问到to
和from
路由对象,它们分别代表目标路由和当前路由。这个守卫可以用来处理路由变化时的逻辑,例如,根据新的路由参数更新组件的状态。
举例来说,当目标参数中的id发生变化时,调用路由更新前守卫:
export default {
watch: {
'$route'(to, from) {
// 对路由变化作出响应...
}
},
beforeRouteUpdate(to, from, next) {
// 在这里可以访问到 to 和 from 路由对象
// 可以执行一些逻辑,比如更新组件状态
this.fetchData(to.params.id);
next();
},
methods: {
fetchData(id) {
// 根据id获取数据
}
}
}
在这个例子中,fetchData
方法会在路由更新时被调用,以确保组件显示的数据是最新的。
以下逐个守卫举例说明。
4.14.5.2 组件内导航守卫钩子
const UserDetails = {
template: `...`,
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
next(vm => {
// 可通过 `vm` 访问组件实例
})
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
// just use `this`
this.name = to.params.name
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (!answer) return false
},
}
组合式API中,通过onBeforeRouteUpdate
和 onBeforeRouteLeave
两个函数分别添加 update 和 leave 守卫。
4.14.5.3 全局前置守卫:beforeEach
当一个导航触发时,全局前置守卫beforeEach
按照路由创建顺序调用。该守卫是异步解析执行,此时导航在所有前置守卫解析完成之前一直处于等待中。
const router = createRouter({ ... })
//参数to:正要进入的目标路由,from:正要离开的原路由
router.beforeEach((to, from) => {
// ...
// 返回false以取消导航,重置回到from的路由;
return false
// 返回路由地址,则重定向到该地址,比如用户登录认证失败或用户登录超时等
//return {name: 'Login'}
// 返回undefined或true,导航仍有效,正常执行下一个导航守卫
})
4.14.5.4 全局导航解析守卫:beforeResovle
解析守卫beforeResovle在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。与beforeEnter类似,在每次导航时都会触发。
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。
4.14.5.5 全局后置守卫:afterEach
afterEach也称全局后置钩子,它不会接受 next
函数也不会改变导航本身,其主要用于分析、更改页面标题、声明页面等辅助功能。
// failure导航失败对象
router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
4.14.5.6 导航守卫中inject注入provide全局状态数据
从Vue3.3开始,可以在导航守卫内使用inject()
方法。在()
中提供的所有内容都可以在 ()
、()
、()
内获取到:
//
const app = createApp(App)
app.provide('global', 'hello injections')
// or
router.beforeEach((to, from) => {
const global = inject('global') // 'hello injections'
// a pinia store
const userStore = useAuthStore()
// ...
})
4.14.5.7 路由独享守卫:beforeEnter
beforeEnter
守卫只在进入不同路由时触发,不会在 params
、query
或 hash
改变时触发。即只有在从一个不同的路由导航时,才会被触发。
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
将一个函数数组传递给 beforeEnter
,为不同的路由重用守卫时做适当的清理工作。也可以通过使用路径meta字段和全局导航守卫来实现类似的行为。
function removeQueryParams(to) {
if (Object.keys(to.query).length)
return { path: to.path, query: {}, hash: to.hash }
}
function removeHash(to) {
if (to.hash) return { path: to.path, query: to.query, hash: '' }
}
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: [removeQueryParams, removeHash],
},
{
path: '/about',
component: UserDetails,
beforeEnter: [removeQueryParams],
},
]
从以上示例中可以看出,beforeRouteUpdate、beforeRouteLeave和beforeRouteEnter属于组件实例的生命周期钩子函数,beforeEach、beforeResovle和afterEach属于路由实例钩子函数,而beforeEnter属具体的路由属性配置回调。