准备知识
vue-router几个钩子函数,vue-router钩子函数官方解释
遇到问题
在用vue-element-admin做一个权限记录时,遇到了一个问题,问题描述:
动态增加的用户权限时,在动态增加权限页面按下F5刷新的时候,会必现无法加载到正确的组件,直接跳转到/404页面
动态增加路由的代码如下:
```javascript
router.beforeEach(async (to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title =getPageTitle(to.meta.title)
// determine whether the user has logged in
const hasToken =getToken()
if (hasToken) {
if (to.path ==='/login') {
// if is logged in, redirect to the home page
next({path:'/'})
NProgress.done()
}else {
const hasGetUserInfo = store.getters.name
if (hasGetUserInfo) {
next()
}else {
try {
// get user info
console.log(`to ----> `, to)
await store.dispatch('user/getInfo')
if (store.getters.roles.length !==0) {// 判断当前用户是否已拉取完user_info信息
const roles = store.getters.roles
const accessRoutes =await store.dispatch('GenerateRoutes', {roles})
router.addRoutes(accessRoutes)
next({...to, replace:true})
}
}catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error ||'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
}
}else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
}else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
```
路由定义:
```javascript
export const constantRouterMap = [
{
path:'/login',
component: () =>import('@/views/login/index'),
hidden:true
},
{
path:'/register',
component: () =>import('@/views/login/register'),
hidden:true
},
{
path:'/edit_password',
component: () =>import('@/views/login/editPassword'),
hidden:true
},
{
path:'/404',
component: () =>import('@/views/404'),
hidden:true
},
{
path:'/',
component: Layout,
redirect:'/dashboard',
children: [{
path:'dashboard',
name:'首页',
component: () =>import('@/views/dashboard/index'),
meta: {title:'首页', icon:'home'}
}]
}
]
},
]
},
// 404 page must be placed at the end !!!
{path:'*', redirect:'/404', hidden:true}
]
export const asyncRouterMap = [
{
path:'/configuration_page',
component: Layout,
children: [
{
path:'/conf_page',
name:'ConfPage',
component: ConfPage,
meta: {title:'配置页面', icon:'conf_page', role: ['sysadmin']}
}
]
}
]
const createRouter = () =>new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({y:0}),
routes: constantRouterMap
})
const router =createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter () {
const newRouter =createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
```
定位问题
1.刷新时在router.beforeEach加了打印回调to的值,发现to此时已经变为/404,所以问题应该不是出现在动态增加路由的地方
2.查看了router官方文档,
完整的导航解析流程导航被触发。
1.在失活的组件里调用离开守卫。
2.调用全局的 beforeEach 守卫。
3.在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
4.在路由配置里调用 beforeEnter。
5.解析异步路由组件。
6.在被激活的组件里调用 beforeRouteEnter。调用全局的 beforeResolve 守卫 (2.5+)。导航被确认。调用全局的 afterEach 钩子。触发 DOM 更新。
7.用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数
官网并没有指明vue是什么时候去匹配路由的,继续网上查找资料,终于在查到了这句话
理论上应该是这样的,但问题是next 方法不是异步的,每当触发router 的勾子时,实际上目标的 route 已经获取完成了,不会再去重新获取,导致即使进行了 commit,也不会去取最新的。可以参考下源码:https://github.com/vuejs/vue-router/blob/d539788df8394efe41c1534e1e9d1555aa2edbe2/src/history/base.js#L163 248这个 route 在执行 next 的时候是不会产生变化的,在第一次导航时候应该就已经确定了。
通过这段话基本确认了在进入beforeEach之前,vue就已经根据静态配置的路由匹配到了/404,这个段话还可以对比vue的源码继续深入研究一下,刷新的具体过程,后续继续学习。
综上的分析:为了让正确的匹配,把/404这个path的匹配到的path放到动态的路由里面,使path不能在静态路由里面匹配,直到动态路由匹配,问题解决。修改代码如下:
```javascript
export const constantRouterMap = [
{
path:'/login',
component: () =>import('@/views/login/index'),
hidden:true
},
{
path:'/register',
component: () =>import('@/views/login/register'),
hidden:true
},
{
path:'/edit_password',
component: () =>import('@/views/login/editPassword'),
hidden:true
},
{
path:'/404',
component: () =>import('@/views/404'),
hidden:true
},
{
path:'/',
component: Layout,
redirect:'/dashboard',
children: [{
path:'dashboard',
name:'首页',
component: () =>import('@/views/dashboard/index'),
meta: {title:'首页', icon:'home'}
}]
}
]
},
]
}
]
export const asyncRouterMap = [
{
path:'/configuration_page',
component: Layout,
children: [
{
path:'/conf_page',
name:'ConfPage',
component: ConfPage,
meta: {title:'配置页面', icon:'conf_page', role: ['sysadmin']}
}
]
},
// 404 page must be placed at the end !!!
{path:'*', redirect:'/404', hidden:true}
]
const createRouter = () =>new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({y:0}),
routes: constantRouterMap
})
const router =createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter () {
const newRouter =createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
```
特别说明
router.addRoutes(accessRoutes)并不会改变router.options.routes的值,router.options.routes在初始化就已经确定了的,
那addRoutes的实现原理待深入分析