Vue Router 实现动态路由和常见问题及解决方法
作者:ryoma 发布时间:2024-05-05 09:09:43
个人理解:动态路由不同于常见的静态路由,可以根据不同的「因素」而改变站点路由列表。常见的动态路由大都是用来实现:多用户权限系统不同用户展示不同导航菜单。
如何利用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
中,在实际项目应用的过程中应考虑数据存储的安全性。
如何实现路由列表解析?
将
JSON
格式的路由信息解析为JavaScript
列表对象;利用列表对象的
filter
方法实现解析函数,通过component
判断是否为布局组件;若为布局组件,使用布局组件代替
component
字符串;若为具体页面,使用
loadView
函数加载对应的具体页面;利用 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 动态路由 刷新后变空白页
来源:https://juejin.im/post/5e6097baf265da57616a9eeb


猜你喜欢
- 本人曾经用过的备份方式有:mysqldump、mysqlhotcopy、BACKUP TABLE 、SELECT INTO OUTFILE,
- while循环是PHP中最简单的循环,其基本格式为:while (expr){ statement } 或者while (ex
- Golang浮点数比较和运算会出现误差。浮点数储存至内存中时,2的-1、-2……-n次方不能精确的表示小数部分,所以再把这个数从地址中取出来
- 一、从外部文档中粘贴时,如果只要文字而不想要其格式,可以使用“Edit→paste as text”命令,不要直接用Ctrl+V。二、当有浏
- 有没有一种方法可以为Django项目中的每个应用程序创建多个自定义错误模板,我的意思是,在我的项目中,我有3个应用程序,每个应用程序将显示3
- JDK1.8安装下载打开链接: http://www.oracle.com/technetwork/java/javase/download
- 本文实例为大家分享了JDBC建立数据库连接的具体代码,供大家参考,具体内容如下import java.sql.DriverManager;i
- 一、Sql Server中的日期与时间函数 1. 当前系统日期、时间 select getdate() 2. dateadd 在向指定日期加
- 1、解决方案mysql是不支持跨库连接的,如果我们实在要连接的话可以用dblink方式。解释:dblink就是我们在创建表的时候连接到我们的
- handle non numerical data举个例子,将性别属性男女转换成0-1,精通ML的小老弟们可以略过本文~~,这里不考虑稀疏向
- 我们的搜索示例仍然相当地简单,特别从数据验证方面来讲;我们仅仅只验证搜索关键值是否为空。 然后许多HTML表单包含着比检测值是否为空更为复杂
- 1、HTML模板和字符串模板HTML模板:直接在HTML页面挂载的模板。(即非字符串模板)非字符串模板:在单文件里用 <templat
- 深度学习中对于网络的训练是参数更新的过程,需要注意一种情况就是输入数据未做归一化时,如果前向传播结果已经是[0,0,0,1,0,0,0,0]
- 看看下面这个刚才提到的下拉列表的例子,就是将Application Object作为一个变量用来存储下拉列表的菜单项的:<%=&nbs
- 学习python编程,首先要配置好环境变量。本文主要讲解python的环境变量配置,在不同版本下如何安装Windows打开Python官方下
- 这篇博客将介绍如何使用Python,Opencv进行二维直方图的计算及绘制(分别用Opencv和Numpy计算),二维直方图可以让我们对不同
- 前面的python3入门系列基本上也对python入了门,从这章起就开始介绍下python的爬虫教程,拿出来给大家分享;爬虫说的简单,就是去
- 下面是一个简单的Family Tree 示例: DECLARE @TT TABLE (ID int,Relation varchar(25)
- 1. test.txt文件,数据以逗号分割,第一个数据为x坐标,第二个为y坐标,数据如下:1.1,22.1,23.1,34.1,540,38
- 本文实例讲述了Python 字符串、列表、元组的截取与切片操作。分享给大家供大家参考,具体如下:demo.py(字符串、列表、元组的截取):