这个问题比较偏向于应用层面,而这方面的代码 vue-element-admin 和 vue-antd-admin 都非常值得学习一番,强烈建议大家克隆下来细品。
对于这道题,回答思路应该从策略,落地到实现,如果能再深入到原理就再好不过了。
那么便开始回答吧~ (๑•̀ㅂ•́)و✧
通常在项目中,我们利用 vue-router 提供的路由守卫保护指定路由的安全。
那路由守卫又是什么呢?
路由守卫
我们先来看看官方的说明:
正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
具体说来,这些路由守卫(官方叫做导航守卫)就是一系列的钩子函数,当 router 准备导航之前会批量的执行这些 hooks,而我们在这些 hooks 中就能做自己想做的事情。
路由守卫主要根据其作用范围分为全局、路由独享以及组件级的,下面分别介绍一下。
全局路由守卫
- 全局前置守卫:
- 注册:
router.beforeEach - 触发:当导航触发的时候,所有注册的
beforeEach将按顺序执行 - 参数:
to:目标路由from:当前路由next:用于 resolve 触发的钩子。next():resolve 当前钩子,进入下一个钩子,如果队列中所有钩子都执行完毕则将导航状态改为 confirmednext(false):中断当前导航(即禁止跳转)next(path) || next({path: '/path'}):跳转到 path 指定的路由next(error):这个在平时工作中用到较少,如果给 next 传入一个 Error 实例,则终止导航,并且将该错误传递给router.onError()注册过的钩子
- 注册:
- 全局解析守卫(版本 2.5.0+)
- 注册:
router.beforeResolve - 触发:在所有组件内守卫和异步路由组件被解析之后,解析守卫将被调用
- 参数:同
router.beforeEach
- 注册:
- 全局后置钩子
- 注册:
router.afterEach - 触发:确认导航之后
- 参数:
to:目标路由from:当前路由
- tip:相信仔细的同学应该发现了,它的名字叫全局后置钩子,而不是全局后置守卫。因为和守卫不同,这类钩子并不接受
next参数,无法改变导航本身,也就是说,用它们是没法保护路由安全的。
- 注册:
路由独享守卫
-
注册
routes: [ { path: '/path', component: Component, beforeEnter: (to, from, next) => {} } ] -
触发:解析异步路由之前
-
参数:同
router.beforeEach
组件内守卫
- 路由前置守卫
- 注册:直接在组件配置内注册
beforeRouteEnter即可 - 触发:解析异步路由之后,执行全局解析守卫之前
- 参数:同
router.beforeEach
- 注册:直接在组件配置内注册
- 路由更新守卫(版本 2.2.0+)
- 注册:直接在组件配置内注册
beforeRouteUpdate即可 - 触发:调用全局前置守卫之后
- 参数:同
router.beforeEach - tip:这个钩子中能访问到组件实例 this
- 注册:直接在组件配置内注册
- 路由后置守卫
- 注册:直接在组件配置内注册
beforeRouteLeave即可 - 触发:导航离开时调用
- 参数:同
router.beforeEach - tip:
- 可以访问组件实例 this
- 实际上在执行流程中非常早就触发了
- 注册:直接在组件配置内注册
执行流程
整个执行流程稍微有些复杂,我们先将几个关键的步骤提出取来:
- 导航触发
- 解析异步路由
- 导航确认
- 更新 DOM
基于上面四个关键步骤,下面我们来看看整个执行流程
- 在导航触发之后,我们要先离开当前路由,去往下一个路由,所以首先调用的是:
beforeRouteLeave - 紧接着是一个新的开始,所以调用全局前置守卫:
beforeEach - 重用的组件并没有销毁,但数据有可能更新,这个时候会调用它们的更新守卫:
beforeRouteUpdate - 紧接着进入到路由配置,调用路由独享组件:
beforeEnter - 这个时候会进行异步路由组件的解析
- 解析完路由之后会进入到新的组件,也会调用其前置守卫:
beforeRouteEnter - 这个时候,路由和组件都已经解析完毕,所以会调用全局解析守卫:
beforeResolve - 至此,导航被确认,于是调用全局后置守卫
afterEach - 最后进入新的路由,触发 DOM 更新
应用
在平时的工作中,用到最多,用于保护指定路由安全的钩子是:beforeEach。在这个钩子中通过角色,token 等判断当前权限,然后在根据权限动态生成不同的路由,以及通过 next 方法来决定是否允许访问目标路由。
这里就以 vue-element-admin 的代码为例:
router.beforeEach((to, from, next) => {
/* 一些初始化操作 */
const hasToken = getToken()
const hasRoles = getRoles()
// 如果有 token,并且有权限则允许访问
if (hasToken && hasRoles) {
next()
// 如果有 token, 却没有权限, 则根据用户权限动态生成路由
} else if (hasToken && !hasRoles) {
createRoutes()
// 没有 token,则检测目标路由是否在白名单内,比如 login、register、404 等
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
原理
关于 vue-router 的原理,如果读过我之前记录的手写简易 vue-router 应该会有比较清晰的思路,这里就大致阐述一下,不再深入,如果感兴趣的同学可以看看这篇。
首先路由要能够根据 URL 的变化而做出响应,那么就要能够监听 URL,vue-router 中最常用的有两种模式:
- hash
- history
abstract 用的非常少
以 hash 为例,我们可以通过 hashchange 监听 hash 的变化。
但仅仅监听 hash 是不够的的,因为路由需要做到的是,当地址发生变化的时候,动态的渲染出目标组件,所以我们用于监听 hash 的值一定要是响应式的,讲一个值变成响应式的在 vue 2.x 中通常有两种方式:
- Vue.util.defineReactive
- 利用 vue 实例的 data 将数据变成响应式
这就是 vue-router 的核心原理了:监听路由变化 + 响应式数据。
下面可以看看本节的脑图,帮助梳理一下思路:
