Vue Router 实现动态路由和常见问题及解决方法


Posted in Javascript onMarch 06, 2020

个人理解:动态路由不同于常见的静态路由,可以根据不同的「因素」而改变站点路由列表。常见的动态路由大都是用来实现:多用户权限系统不同用户展示不同导航菜单。

如何利用Vue Router 实现动态路由

Vue项目实现动态路由的方式大体可分为两种:

  • 前端将全部路由规定好,登录时根据用户角色权限来动态展示路由;
  • 路由存储在数据库中,前端通过接口获取当前用户对应路由列表并进行渲染;

第一种方式在很多Vue UI Admin上都实现了,可以去读一下他们的源码理解具体的实现思路,这里就不过多展开。第二种方式现在来说也比较常见了,因为近期项目正好用到所以单独讲一下,这里我使用的方案是利用Vue Router的一些特性实现后端主导的动态路由。

使用到的功能特性

Vue Router 全局前置守卫

官网解释

这里我们主要借助全局前置守卫的「前置」特性,在页面加载前将当前用户所用到的路由列表注入到Router实例中,注入使用到的方法则是下面的router.addRoutes方法。

Vue Router router.addRoutes 实例方法

官网解释

router.addRoutes方法可以为Router实例动态添加路由规则,刚好为我们实现动态路由提供了注入方法。

Vue Router 路由懒加载

官网解释

懒加载这个功能不是动态路由的必要功能,但既然提供了这一特性,所以就直接在项目中使用了。

具体思路

基础信息准备

前端代码实现基本静态路由,例如:登录页路由,服务器错误页路由等(这里有一个坑,后面讲)。数据库存储全部动态路由信息。

数据库如何存储动态路由信息?我选择的方案是现将路由引用的对象字符串化,再将路由列表转化为JSON格式传输给后端,经后端处理后存储到数据库里。总之在前后端进行传递的是JSON格式的路由列表信息。

如何将路由中引用的对象字符串化?我遇到的实际问题是:使用的UI组件提供了布局方案,需要引用布局组件并在子路由处引用具体页面。我选择的解决方案是:区别对待需要引用布局组件的component属性,使用简短字符串代替布局组件,使用文件路径字符串代替页面引入。具体实现可以看后面的代码实例。

利用全局前置守卫对路由信息进行判断

1-判断用户是否登录1.1-若未登录,跳转至登录页面1.2-若已经登录,判断是否已获取路由列表1.2.1-若未获取,从后端获取、解析并保存到Vuex中1.2.2-若已获取,跳转至目标页面

这里我没做太多考察,直接将取到数据存储到了Vuex中,在实际项目应用的过程中应考虑数据存储的安全性。

如何实现路由列表解析?

  1. JSON格式的路由信息解析为JavaScript列表对象;
  2. 利用列表对象的filter方法实现解析函数,通过component判断是否为布局组件;
  3. 若为布局组件,使用布局组件代替component字符串;
  4. 若为具体页面,使用loadView函数加载对应的具体页面;
  5. 利用 router.addRoutes 方法动态添加路由

这一步就很简单了,将解析好的路由列表通过router.addRoutes方法添加到Router实例中即可。

简单的实现代码

// router/index.js
import Vue from 'vue'
import store from '@/store'
import Router from 'vue-router'
import { getToken } from '@/lib/util'

Vue.use(Router)

// 定义静态路由
const staticRoutes = [
 {
 path: '/login',
 name: 'login',
 meta: {
 title: '登录页面',
 hideInMenu: true
 },
 component: () => import('@/view/login/login.vue')
 },
 {
 path: '/401',
 name: 'error_401',
 meta: {
 hideInMenu: true
 },
 component: () => import('@/view/error-page/401.vue')
 },
 {
 path: '/500',
 name: 'error_500',
 meta: {
 hideInMenu: true
 },
 component: () => import('@/view/error-page/500.vue')
 }
]

// 定义登录页面名称(为了方便理解才定义的)
const LOGIN_PAGE_NAME = 'login'

// 实例化 Router 对象
const router = new Router({
 staticRoutes,
 mode: 'history'
})

// 定义全局前置守卫(里面有两个坑要注意)
router.beforeEach((to, from, next) => {
 // 通过自定义方法获取用户 token 用来判断用户登录状态
 const token = getToken()
 if (!token && to.name !== LOGIN_PAGE_NAME) {
 // 如果没有登录而且前往的页面不是登录页面,跳转到登录页
 next({ name: LOGIN_PAGE_NAME })
 } else if (!token && to.name === LOGIN_PAGE_NAME) {
 // 如果没有登录而且前往的页面是登录页面,跳转到登录页面
 // 这里有一个坑,一定要注意这一步和上一步得分开写
 // 如果把前两步判断合并为 if (!token) next({ name:login })
 // 则会形成登录页面无限刷新的错误,具体成因后面解释
 next()
 } else {
 // 如果登录了
 if (!store.state.app.hasGetRoute) {
 // 如果没有获取路由信息,先获取路由信息而后跳转
 store.dispatch('getRouteList').then(() => {
 router.addRoutes(store.state.app.routeList)
 // 这里也是一个坑,不能使用简单的 next()
 // 如果直接使用 next() 刷新后会一直白屏
 next({ ...to, replace: true })
 })
 } else {
 // 如果已经获取路由信息,直接跳转
 next()
 }
 }
})

export default router
// store/index.js
import router from '@/router'
import Main from '@/components/main'
import { getToken } from '@/lib/util'
import { getRoute } from '@/api/app'

const loadView = (viewPath) => {
 // 用字符串模板实现动态 import 从而实现路由懒加载
 return () => import(`@/view/${viewPath}`)
}

const filterAsyncRouter = (routeList) => {
 return routeList.map((route) => {
 if (route.component) {
 if (route.component === 'Main') {
 // 如果 component = Main 说明是布局组件
 // 将真正的布局组件赋值给它
 route.component = Main
 } else {
 // 如果不是布局组件就只能是页面的引用了
 // 利用懒加载函数将实际页面赋值给它
 route.component = loadView(route.component)
 }
 // 判断是否存在子路由,并递归调用自己
 if (route.children && route.children.length) {
 route.children = filterAsyncRouter(route.children)
 }
 }
 })
}

export default {
 state: {
 routeList: [],
 token: getToken(),
 hasGetRoute: false
 },
 mutations: {
 setRouteList(state, data) {
 // 先将 JSON 格式的路由列表解析为 JavaScript List
 // 再用路由解析函数解析 List 为真正的路由列表
 state.routeList = filterAsyncRouter(JSON.parse(data))
 // 修改路由获取状态
 state.hasGetRoute = true
 }
 },
 atcions: {
 getRouteList({ state, commit }) {
 return new Promise((resolve) => {
 const token = state.token
 getRoute({ token }).then((res) => {
 let data = res.data.data
 // 注意这里取出的是 JSON 格式的路由列表
 commit('setRouteList', data)
 resolve()
 })
 })
 }
 }
}

常见问题

页面卡在登录页面而且不断刷新

这个问题的解决方案在「实现代码」中已经提到了,只需要在判断登录状态的时候注意不要将两种未登录状态混为一谈即可。但这样治标不治本,因为同样的问题可以由不同形式的代码导致,那导致问题的原因是什么那?然我们慢慢分析:

我们先假设不小心把两种未登录的状态混在一起判断:

if (!token) {
 next({ name: LOGIN_PAGE_NAME })
}

这里的next({ name: LOGIN_PAGE_NAME })方法会再一次激活全局前置守卫,从而导致再一次进入判断并触发next({ name: LOGIN_PAGE_NAME }),如此递归调用下去,页面就会卡主并且不断刷新。

动态路由配合路由懒加载

实现这一目的的方案也在代码示例中展示了:

const loadView = (viewPath) => {
 return () => import(`@/view/${viewPath}`)
}

这里是运用了一个 JavaScript 不太常用的特性:字符串模板,使用此特性让不支持字符串拼接的import操作能够实现动态import不同的模块。

动态路由刷新后 404

这应该是本方案中最常见的一个错误之一,其原意是很多人在创建「基本静态路由」的时候回把 404 页面的路由也加入在里面,从而导致页面加载初期动态路由还没有加入到路由实例中,匹配范围最广的 404 页面就会跳出来。解决方法就是将 404 页面的路由也加入到动态路由中。

动态路由刷新后变空白页

造成这一问题的原因有很多,我这里遇到的问题是使用参考文章3解决的,但具体原理我还没弄清楚,等我做一下研究再来更新。

动态路由页面刷新时 Title 不稳定

造成这一问题的原因很简单:因为页面刷新的时候路由信息还没加载进来,所以根本没有标题信息可供加载。但是我还没找到比较好的解决方案,同样等我研究一下再更新。

参考大师兄:

Vue 动态路由的实现……

Vue Router 文档页面

rambo:vue router 动态路由 刷新后变空白页

总结

到此这篇关于Vue Router 实现动态路由和常见问题及解决方法的文章就介绍到这了,更多相关vue router 动态路由内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
JS获取地址栏参数的小例子
Aug 23 Javascript
event对象获取方法总结在google浏览器下测试
Nov 03 Javascript
checkbox选中与未选中判断示例
Aug 04 Javascript
JQuery遍历DOM节点的方法
Jun 11 Javascript
Bootstrap 折叠(Collapse)插件用法实例详解
Jun 01 Javascript
AngularJS模板加载用法详解
Nov 04 Javascript
利用jQuery解析获取JSON数据
Apr 08 jQuery
javascript高级模块化require.js的具体使用方法
Oct 31 Javascript
详解tween.js 中文使用指南
Jan 05 Javascript
浅析Visual Studio Code断点调试Vue
Feb 27 Javascript
js实现上传图片并显示图片名称
Dec 18 Javascript
Vue中引入svg图标的两种方式
Jan 14 Vue.js
Vue基于iview实现登录密码的显示与隐藏功能
Mar 06 #Javascript
Vue状态模式实现窗口停靠功能(灵动、自由, 管理后台Admin界面)
Mar 06 #Javascript
javascript中可能用得到的全部的排序算法
Mar 05 #Javascript
js的Object.assign用法示例分析
Mar 05 #Javascript
Element的el-tree控件后台数据结构的生成以及方法的抽取
Mar 05 #Javascript
element-ui树形控件后台返回的数据+生成组织树的工具类
Mar 05 #Javascript
vue中使用vue-print.js实现多页打印
Mar 05 #Javascript
You might like
PHP将数据导出Excel表中的实例(投机型)
2017/07/31 PHP
php封装实现钉钉机器人报警接口的示例代码
2020/08/08 PHP
跨浏览器的设置innerHTML方法
2006/09/18 Javascript
jQuery Div中加载其他页面的实现代码
2009/02/27 Javascript
JavaScript DOM 学习第二章 编辑文本
2010/02/19 Javascript
Jquery之Ajax运用 学习运用篇
2011/09/26 Javascript
jQuery AjaxQueue改进步骤
2011/10/06 Javascript
JS实现点击颜色块切换指定区域背景颜色的方法
2015/02/25 Javascript
C#中使用迭代器处理等待任务
2015/07/13 Javascript
12种JavaScript常用的MVC框架比较分析
2015/11/16 Javascript
js实现prototype扩展的方法(字符串,日期,数组扩展)
2016/01/14 Javascript
手机Web APP如何实现分享多平台功能
2016/08/19 Javascript
node.js express安装及示例网站搭建方法(分享)
2016/08/22 Javascript
不间断循环滚动效果的实例代码(必看篇)
2016/10/08 Javascript
protractor的安装与基本使用教程
2017/07/07 Javascript
解决vue-router中的query动态传参问题
2018/03/20 Javascript
JS实现水平遍历和嵌套递归操作示例
2019/08/15 Javascript
jQuery实现动态加载瀑布流
2020/09/01 jQuery
js用正则表达式筛选年月日的实例方法
2021/01/04 Javascript
基于python 处理中文路径的终极解决方法
2018/04/12 Python
django admin 后台实现三级联动的示例代码
2018/06/22 Python
面向初学者的Python编辑器Mu
2018/10/08 Python
python验证码图片处理(二值化)
2019/11/01 Python
Tensorflow中tf.ConfigProto()的用法详解
2020/02/06 Python
Python实现捕获异常发生的文件和具体行数
2020/04/25 Python
详解HTML5中的拖放事件(Drag 和 drop)
2016/11/14 HTML / CSS
南非最大的花卉和送礼服务:NetFlorist
2017/09/13 全球购物
I.T集团香港官方商城:ITeSHOP.com Hong Kong
2019/02/15 全球购物
Ray-Ban雷朋太阳眼镜英国官网:Ray-Ban UK
2019/11/23 全球购物
递归实现回文判断(如:abcdedbca就是回文,判断一个面试者对递归理解的简单程序)
2013/04/28 面试题
创建学习型党组织实施方案
2014/03/29 职场文书
竞选学习委员演讲稿
2014/04/28 职场文书
文员求职信
2014/07/15 职场文书
运动会跳远广播稿5篇
2014/09/17 职场文书
个人道歉信大全
2019/04/11 职场文书
详细介绍python操作RabbitMq
2022/04/12 Python