用vue-router开发权限中碰到的坑:addRoutes使用遇到的坑

本文详细解析了在使用Vue Router的addRoutes API动态添加路由时遇到的重复路由名警告问题,阐述了问题原因及两种解决策略:强制刷新页面与重置router的match属性,提供了一种优雅的解决方案。

 

 

最近项目中使用了vue-router的addRoutes这个api,遇到了一个小坑,记录总结一下。

场景复现:
做前端开发的同学,大多都遇到过这种需求:页面菜单根据用户权限动态生成,一个常见的解决方案是:

前端初始化的时候,只挂载不需要权限路由,如登陆,注册等页面路由,然后等用户登录之后,后端返回当前用户的权限表,前端根据这个权限表遍历前端路由表,动态生成用户权限路由,然后使用vue-router提供的addRoutes,将权限路由表动态添加到路由实例中,整个过程大致如下:

 
  1. // router.js 文件

  2.  
  3. // 需要用户权限的路由表

  4. const appRoutes = [

  5. {

  6. path: '/dashboard',

  7. name: 'dashboard',

  8. component: () => import('...'),

  9. children: [

  10. RouteConfig1,

  11. RouteConfig2,

  12. ...

  13. ]

  14. },

  15. RouteConfig,

  16. ...

  17. ];

  18.  
  19. // 不需要用户权限的路由表

  20. const constantRoutes = [

  21. {

  22. path: '/login',

  23. name: 'login',

  24. component: Login

  25. },

  26. {

  27. path: '/register',

  28. name: 'register',

  29. component: Register

  30. },

  31. ...

  32. ]

  33.  
  34. // 初始化路由的时候,只挂载不需要用户权限的路由表

  35. const router = new VueRouter({

  36. mode: 'history',

  37. base: process.env.BASE_URL,

  38. constantRoutes

  39. });

  40.  
  41.  
  42. /**

  43. *

  44. * 假如后端返回的数据格式如下:

  45. *

  46. * {

  47. * status: 200,

  48. * message: 'successful',

  49. * data: {

  50. * user: {...},

  51. * token: '...',

  52. * permisssion: [...]

  53. * }

  54. * }

  55. *

  56. * login.vue

  57. */

  58. axios.post('/user/login',{username,password})

  59. .then(res => {

  60. if (res.status === 200) {

  61. // 如果登录成功,则需要遍历生成用户权限路由

  62. // filterRoutes根据permission和router.js中定义的appRoutes生成动态路由表

  63. const routes = filterRoutes(permission);

  64.  
  65. // 然后使用addRoutes将routes挂载到router中

  66. router.addRoutes(routes);

  67. } else {

  68. ...

  69. }

  70. })

  71. .catch(error => { ... })

  72.  
  73. 复制代码

写到这里,貌似动态生成路由的功能就好了,一切都perfect了,但问题紧接着就来了,当用户登录之后,我们点击页面上的退出按钮退出当前登录,然后重新登录,会发现浏览器console面板紧接着就报如下错误:

 

 

纳尼(⊙o⊙)?这是怎么回事呢,第二次登录也正常登录了,功能上似乎没有什么问题,但这个警告从哪里来的呢?对于一个重度强迫症患者来说,任何警告和报错都是不允许出现的,哪怕功能上没什么问题。

捋一捋

这段警告的意思是说,以上的这几个路由命名重复,存在多个name相同的路由。那么为什么会有多个路由名称相同的路由呢?

让我们从头捋一下这个错误是怎么来的。首先第一次打开网站登录的时候是没有问题的,只有当我们退出登录,重新登录的时候,这段警告就来了。并且如果我们在重复登录之前刷新一下浏览器然后再登录,这种警告就不会出现了,很神奇是不是?

分析一下上面的情景:首先这个警告只会在用户重新登录的时候出现,登录的时候我们做的唯一跟路由相关的事情就是动态添加路由,所以问题肯定出在 router.addRoutes(routes)这里,其次这里又分了两种情况:有刷新和无刷新。在无刷新的情况下会报这个警告,有刷新就不会报这个警告。那么有刷新和无刷新有什么区别呢?

我们很容易就想到,当页面刷新的时候,Vue实例会重新初始化,Vue实例初始化的过程中,挂载在它上面的Vue-Router,Store等内容也会重新初始化。而在不刷新的情况下,就不会重新初始化。

再想想,我们第一次登录之后,通过addRoutes添加了权限路由routes到router上,假设我们这个权限routes中包括了dashboard,user,role三个路由,那么当我们退出登录,然后重新登录的时候,由于同一个用户登录,后端返回的权限列表是一样的,生成的动态路由routes也是一样的(即里面同样包含了dashboard,user,role三个路由),那么此时再次添加这三个路由就导致router中挂载的routes重复。而在刷新的情况下,由于router重新初始化,只包含了初始化我们添加的不需要权限的路由,此时再次登录,重新添加就不存在路由重复的问题了。

通过以上的分析,我们搞清了问题的来源,那么如何解决呢,很遗憾,vue-router并没有删除路由的api。根据以上的分析,我们很容易想到,通过强制刷新页面的方式来重置router:即当用户退出登录的时候,通过js强制刷新一下页面。就可以解决问题。这种方式虽然可以解决问题,但显得不是很优雅,而且刷新页面导致资源重新加载和页面闪烁,体验也不是特别好。因此有没有在不刷新的情况下解决问题的办法呢?

经过一番搜索,终于找到了一种方法,即重置当前router的match属性:

 
  1. router.js

  2.  
  3. // 定义一个函数来创建router

  4. export const createRouter = routes => new VueRouter({

  5. mode: 'history',

  6. base: process.env.BASE_URL,

  7. routes

  8. });

  9.  
  10. // 在使用addRoutes的地方

  11. // 重置当前router的match = 初始router.match

  12. router.match = createRouter(constantRoutes).match;

  13. router.addRoutes(routes);

  14.  
  15. 复制代码

这样就可以完美解决问题了。

总结:
整个解决的过程还是比较痛苦的,因为实际中我的代码是比较复杂的,并不像上面简化后那么简单。整个addRoutes是在store.dispatch中完成,并且中间还夹杂着生成动态路由,根据动态路由再生成用户菜单等一系列功能,干扰比较大,并且这个是源码报警,不好定位,只能通过console和浏览器调试,一步步缩小报错范围,最终找到问题原因。然后再通过google,以及搜索vue-router仓库的issue一步步找到解决方法。所以想说,如果大家开发中遇到一些第三方依赖的问题,可以去搜索官方仓库的issue,很好用的,很多问题其实issue中都有答案。我是屡试不爽。最后,一定要用google,垃圾百度,浪费我好长时间,啥都没找到

 

https://blog.youkuaiyun.com/weixin_34179968/article/details/91366145?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

 

 

<think>我们正在解决VueRouter中出现的重复命名路由定义的问题。根据引用内容,这个警告是因为路由的name属性重复导致的。VueRouter要求每个路由的name必须是唯一的,否则在开发环境下会发出警告,并可能导致路由行为异常。解决方案的核心是确保所有路由(包括静态定义和动态添加的路由)的name属性都是唯一的。根据引用[2]和引用[3]的建议,我们可以采取以下方法:1.对于静态路由(如constantRoutes),检查是否有重复的name,并确保每个路由的name唯一。2.对于动态添加的路由,在生成动态路由数组时,为每个路由生成一个唯一的name。引用[2]提供了两种生成唯一name的方法:-方法一:使用路由的path加上随机字符串(确保唯一性)-方法二:将路由path中的'/'替换为'_'(这种转换不一定绝对唯一,但可以避免大部分重复,如果path相同则仍会重复,所以需要确保path唯一或配合其他方法)另外,引用[4]指出,VueRouter内部在添加路由时会检查name是否已存在,如果已存在则会发出警告。因此,我们可以按照以下步骤操作:步骤1:检查静态路由在router.js中,检查所有静态路由(constantRoutes)的name是否重复。如果有重复,修改为不同的name。步骤2:处理动态路由在动态生成路由数组时,使用一个函数来确保每个路由的name唯一。可以采用引用[2]中的方法,但要注意:-如果使用随机字符串,虽然能保证唯一,但每次刷新页面时动态路由的name都会变化,这可能会影响通过name进行的路由跳转(因为name变了)。-所以,更推荐的方式是,由后端返回的路由信息中确保每个路由有一个唯一的标识(如id),或者在前端生成时根据路由的path和唯一标识生成一个name(但要确保同一个动态路由每次生成的name相同,否则刷新后可能无法匹配)。实际上,更好的做法是在后端返回的路由数据中给每个路由配置一个唯一的name,这样前端直接使用即可。如果后端没有返回name,或者需要前端生成,我们可以使用路由的path来生成name(但要确保path唯一),将path中的'/'替换为'_'(例如:/user/info->user_info),然后检查是否与已有的name重复,重复则再附加其他标识(如父路由的name等)。或者,我们可以使用一个函数来生成唯一name,并确保同一个路由每次生成的name相同(不依赖随机数)。例如,使用路由的path(确保path是唯一的)作为name的基础,再根据路由的层级添加前缀(父路由的name)等。步骤3:避免重复添加动态路由除了name重复,还有可能是多次调用了router.addRoutes方法添加了相同的路由(即使name相同,但重复添加也会导致警告)。所以,我们应确保动态路由只被添加一次。在Vue项目中,通常是在用户登录后根据权限动态添加路由,因此需要保证在用户登录后只添加一次动态路由。可以考虑在登录成功后添加动态路由,并记录状态(如vuex中标记已添加),避免重复添加。步骤4:修改VueRouter.prototype.push方法(可选)在引用[1]中,为了防止路由跳转时出现未处理的Promise错误,重写了push方法。这并不直接解决重复路由问题,但可以避免一些其他问题。代码实现:1.静态路由检查:在定义constantRoutes数组时,确保每个对象的name属性(如果有)都是唯一的。2.动态路由处理函数(根据引用[2]修改):我们可以写一个函数,在添加动态路由之前,为每个路由生成唯一的name。这里采用将path转换为name的方法,并确保该name在当前静态路由和动态路由中唯一。但注意,由于动态路由是后来添加的,我们可以在生成动态路由时,同时检查已有的路由(包括静态路由)中是否已经存在相同的name,如果存在,则在生成的名字后添加序号等。然而,这个操作比较复杂,而且VueRouter没有提供获取所有已存在路由name的方法。我们可以考虑在生成动态路由时,将动态路由的name统一加上一个前缀(比如'dynamic_')以避免与静态路由冲突,然后使用path生成剩余部分。但是,如果动态路由之间有相同的path(需要避免,因为path相同但参数不同的路由应该用同一个路由定义,通过不同的参数来区分),或者同一个动态路由被多次添加,则会导致重复。因此,更实际的做法是:a.在定义路由数据时,确保每个路由的path是唯一的(除了带参数的情况,但参数路由可以视为相同path,使用同一个路由定义)。b.在生成name时,将path中的非字母数字字符替换,并保证同一个路由每次生成的name相同。考虑到通过name跳转的便利性,我们可能希望动态路由的name是固定的。因此,最好是由后端返回一个唯一的name。如果后端确实没有返回,我们只能在前端生成,那么我们可以这样写:```javascript//生成一个基于路由path的name,将path中的'/'替换为'_',并去掉开头和结尾的'_'(如果必要)functiongenerateRouteName(route){letname=route.path.replace(/^\//,'').replace(/\/$/,'').replace(/\//g,'_');//如果name为空,则使用一个默认值if(!name){name='route_'+(Math.random().toString(36).substr(2,8));//随机字符串作为备用}returnname;}```然后在生成动态路由时使用:```javascriptconstgenerateRoutes=(routes)=>{returnroutes.map(route=>{//如果route没有name,则生成一个if(!route.name){route.name=generateRouteName(route);}if(route.children){route.children=generateRoutes(route.children);}returnroute;});};```但是,这种方法不能保证绝对唯一(比如两个不同的路由有相同的path格式,比如都有path:'/user',那么生成的name都是'user',就会重复)。因此,我们需要检查避免重复,或者要求后端返回唯一name。在引用[2]中,还提到可以在生成name时加上随机字符串,但是这样会导致每次刷新生成的name不同,所以不推荐。因此,如果使用前端生成,我们需要维护一个已有的name集合,确保在同一个应用运行期间生成的动态路由name不重复。我们可以这样做:```javascript//在生成动态路由的函数外部,我们可以通过遍历已有的路由记录(包括静态路由)来获取已有的name集合//但是,在动态路由生成前,我们无法直接获取静态路由的name?实际上,静态路由已经在router实例中了,但此时动态路由还没添加,所以我们可以通过router实例的options.routes来获取(但注意,这包括之前添加的所有静态路由和动态路由,而动态路由可能是多次添加的?所以需要谨慎)。//更好的方式:我们在生成动态路由之前,先获取当前router中已有的所有路由记录的name,然后确保生成的新路由name不重复。functiongenerateUniqueRouteNames(routes,existingNames){constnamesSet=newSet(existingNames);//已有的name集合functioninner(routesList){returnroutesList.map(route=>{//如果当前路由没有name,则生成一个if(!route.name){letbaseName=route.path.replace(/^\//,'').replace(/\/$/,'').replace(/\//g,'_')||'route';letnewName=baseName;letcount=1;//如果生成的name已经存在,则添加后缀直到唯一while(namesSet.has(newName)){newName=baseName+'_'+count;count++;}route.name=newName;namesSet.add(newName);}else{//如果已经有name,检查是否重复if(namesSet.has(route.name)){//如果重复,则需要修改letbaseName=route.name;letnewName=baseName;letcount=1;while(namesSet.has(newName)){newName=baseName+'_'+count;count++;}route.name=newName;namesSet.add(newName);}else{namesSet.add(route.name);}}if(route.children){route.children=inner(route.children);}returnroute;});}returninner(routes);}//使用示例://首先,获取当前路由实例中已有的所有路由的nameconstrouterOptions=router.options.routes;//注意:这里只包括初始化时的静态路由和之前调用addRoutes添加的路由(但注意:addRoutes添加的路由在router.options.routes中不会自动更新,所以这个方法不准确!)```这里有一个问题:router.options.routes只包含初始化时的路由。使用router.addRoutes动态添加的路由并不会被加入到router.options.routes中。因此,我们无法通过router.options.routes获取所有路由。那么,我们如何获取已有的路由name?VueRouter没有提供直接的API。一个替代方法是自己维护一个已添加动态路由的列表(在vuex中存储)或者避免重复添加动态路由(即每次添加前先重置路由)。因此,另一种思路是:在每次添加动态路由前,重置路由到只有静态路由的状态,然后再添加新的动态路由。这样,我们只需要处理静态路由中已有的name,动态路由每次都是新生成的,只需要保证新生成的一批动态路由内部不重复,以及和静态路由不重复。重置路由的方法(使用新的VueRouter实例替换原有的router):```javascript//创建一个新的VueRouter实例constcreateRouter=()=>newVueRouter({mode:'hash',//和原来一致base:process.env.BASE_URL,routes:constantRoutes//静态路由});//在应用中使用这个routerletrouter=createRouter();//重置路由的函数functionresetRouter(){constnewRouter=createRouter();router.matcher=newRouter.matcher;//替换matcher,从而重置路由}//在添加动态路由前重置resetRouter();//然后生成动态路由并添加router.addRoutes(dynamicRoutes);```这样,每次添加动态路由前重置,那么我们可以确保动态路由不会重复添加(因为之前添加的已经被清除了),并且此时只有静态路由。然后我们只需要在生成动态路由时,确保动态路由内部的name不重复,并且与静态路由中的name不重复。因此,我们可以:```javascript//获取静态路由的name集合conststaticRouteNames=[];functiontraverse(routes){routes.forEach(route=>{if(route.name){staticRouteNames.push(route.name);}if(route.children){traverse(route.children);}});}traverse(constantRoutes);conststaticNameSet=newSet(staticRouteNames);//生成动态路由的函数(只需要避免与静态路由重复以及自身重复)functiongenerateUniqueRouteNames(routes,staticNames){constnamesSet=newSet(staticNames);//静态路由的nameconstdynamicNamesSet=newSet();//用于记录动态路由生成的name,避免内部重复functioninner(routesList){returnroutesList.map(route=>{//如果route有name,先检查是否在静态路由中已存在或在动态路由中已生成(重复)if(route.name){if(namesSet.has(route.name)||dynamicNamesSet.has(route.name)){//重复,需要修改letbaseName=route.name;letnewName=baseName;letcount=1;while(namesSet.has(newName)||dynamicNamesSet.has(newName)){newName=baseName+'_'+count;count++;}route.name=newName;dynamicNamesSet.add(newName);}else{dynamicNamesSet.add(route.name);}}else{//没有name,生成一个letbaseName=route.path.replace(/^\//,'').replace(/\/$/,'').replace(/\//g,'_')||'route';letnewName=baseName;letcount=1;while(namesSet.has(newName)||dynamicNamesSet.has(newName)){newName=baseName+'_'+count;count++;}route.name=newName;dynamicNamesSet.add(newName);}if(route.children){route.children=inner(route.children);}returnroute;});}returninner(routes);}//然后使用constdynamicRoutesWithUniqueNames=generateUniqueRouteNames(accessRoutes,staticRouteNames);```步骤:重置路由->生成唯一name的动态路由->添加动态路由。总结解决方案:1.在静态路由中确保每个路由的name唯一。2.对于动态路由,在添加前先重置路由(将路由恢复到只有静态路由的状态,避免之前动态路由的影响)。3.在生成动态路由的函数中,确保每个路由的name唯一(包括避免与静态路由重复以及动态路由内部重复)。4.然后添加动态路由。注意:重置路由会导致之前动态添加的路由被移除,所以在添加新的动态路由之前,我们需要重新生成权限路由(根据用户权限)。代码示例:在vue项目中,通常在permission.js(或类似的地方)进行路由守卫和动态路由添加。下面是一个简化的示例:```javascriptimportrouterfrom'./router'importstorefrom'./store'//重置路由的函数(在router.js中定义并导出)//假设在router.js中已经导出了resetRouter方法//或者在这里定义://创建一个新的router(只包含静态路由)并替换当前router的matcherconstcreateRouter=(routes)=>newVueRouter({mode:'hash',base:process.env.BASE_URL,routes})exportfunctionresetRouter(){constnewRouter=createRouter(constantRoutes)//constantRoutes是静态路由router.matcher=newRouter.matcher//替换matcher}//在权限控制的地方(如路由守卫)router.beforeEach((to,from,next)=>{if(store.getters.token){//有tokenif(to.path==='/login'){next({path:'/'})}else{//检查用户是否已经获取了用户信息(和权限路由)if(store.getters.routes.length===0){//没有获取,则获取用户信息store.dispatch('GetUserInfo').then(()=>{store.dispatch('GenerateRoutes').then(accessRoutes=>{//重置路由resetRouter()//生成动态路由(确保name唯一)constuniqueAccessRoutes=generateUniqueRouteNames(accessRoutes,staticRouteNames)//staticRouteNames是静态路由的name数组//添加动态路由router.addRoutes(uniqueAccessRoutes)//将动态路由信息存入vuex,用于生成菜单等store.commit('SET_ROUTES',uniqueAccessRoutes)//因为路由是动态添加的,所以需要重定向到目标路由next({...to,replace:true})})})}else{next()}}}else{//没有tokenif(whiteList.includes(to.path)){next()}else{next('/login')}}})```这样,每次用户登录后生成动态路由,并确保不会出现name重复的问题。注意:上面的示例代码仅为示意,实际项目中需要根据情况调整。另外,如果您的动态路由不需要通过name进行跳转,也可以不使用name(删除name属性),这样就不会有重复name的警告。但是,如果路由有name,则在编程式导航时使用name会更方便(特别是路径很长或者有参数时)。因此,我们还是推荐确保name唯一。以上为解决重复命名路由定义的方法。</think> ### 解决 Vue Router 重复命名路由定义问题 在 Vue Router 中出现 `[vue-router] Duplicate named routes definition` 警告的根本原因是**路由名称重复**。Vue Router 要求每个路由的 `name` 属性必须是唯一的,否则会导致路由行为不可预测(如导航混乱、重定向错误等)。以下是完整解决方案: #### 核心解决步骤 1. **检查静态路由配置** - 在 `constantRoutes` 中手动检查是否有重复的 `name` - 特别注意父路由和子路由的命名冲突 ```javascript {5,12} // router.js export const constantRoutes = [ { path: '/login', name: 'login', // 确保唯一 component: () => import('@/views/Login.vue') }, { path: '/dashboard', name: 'dashboard', // 确保唯一 component: Layout, children: [ { path: '', name: 'dashboard-index', ... } // 子路由也需要唯一名 ] } ] ``` 2. **修复动态路由生成逻辑**(最常见原因) - 使用路径哈希或唯一标识符生成路由名 ```javascript // 动态路由生成函数 const generateUniqueRouteName = (route) => { // 方法1:基于路径生成 (推荐) const baseName = route.path.replace(/\//g, '_').replace(/^_|_$/g, '') // 方法2:添加随机后缀保证唯一性 const uniqueSuffix = Math.random().toString(36).substr(2, 6) return `${baseName}_${uniqueSuffix}` // 示例: user_info_x3f5k2 } // 在添加动态路由时应用 store.dispatch('getAsyncRoutes').then(routes => { const processedRoutes = routes.map(route => ({ ...route, name: route.name || generateUniqueRouteName(route) // 优先用后台提供名 })) router.addRoutes(processedRoutes) }) ``` 3. **路由添加防护机制** ```javascript // 防止重复添加路由 let isDynamicRoutesAdded = false // 状态标记 router.beforeEach((to, from, next) => { if (!isDynamicRoutesAdded && store.getters.userRole) { store.dispatch('generateRoutes').then(dynamicRoutes => { router.addRoutes(dynamicRoutes) isDynamicRoutesAdded = true next({ ...to, replace: true }) // 重新导航 }) } else { next() } }) ``` #### 关键注意事项 1. **调试建议**: - 开发环境启用 [Vue Devtools](https://devtools.vuejs.org/) 检查路由树 - 使用调试代码打印当前路由表: ```javascript console.log(router.getRoutes().map(r => r.name)) ``` 2. **特殊场景处理**: - **嵌套路由**:父/子路由都需要唯一名 - **路由模块化**:合并路由时使用命名空间 ```javascript // adminRoutes.js export default [{ path: '/admin', name: 'admin-namespace', // 命名空间前缀 children: [...] }] ``` 3. **修复底层 push 方法**(预防导航报错) ```javascript {9} // router.js const originalPush = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { return originalPush.call(this, location).catch(err => { if (err.name !== 'NavigationDuplicated') { console.error('Routing error:', err) } }) } ``` #### 故障排查流程图 ```mermaid graph TD A[出现Duplicate警告] --> B{检查路由表} B -->|静态路由| C[查找constantRoutes的重复name] B -->|动态路由| D[检查addRoutes调用点] D --> E[确认动态路由name生成逻辑] E -->|无唯一处理| F[添加唯一名生成函数] E -->|有唯一处理| G[检查重复调用] G --> H[添加路由添加状态锁] C --> I[修正冲突name] F --> J[重新测试] H --> J I --> J ``` > **为何必须修复**:路由名称重复可能导致 [重定向错误](https://router.vuejs.org/guide/essentials/redirect-and-alias.html#redirect) 和 [导航守卫失效](https://router.vuejs.org/guide/advanced/navigation-guards.html)。生产环境虽然不会显示警告,但会出现不可预测的路由行为[^3][^4]。 ### 相关问题 1. 如何验证 Vue 路由名称的唯一性? 2. 动态路由添加后如何实现页面自动刷新? 3. Vue Router 4 中如何处理路由重复命名问题? 4. 路由权限控制如何避免与路由命名冲突? 5. 路由重置的正确实现方式是什么? [^1]: vue2解决动态路由反复登录警告 [^2]: vue 路由报[vue-router] Duplicate named routes definition [^3]: bug(警告):[vue-router] Duplicate named routes definition [^4]: Vue----报警告:[vue-router] Duplicate named routes definition
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值