vue-element-admin解决三级目录的KeepAlive缓存问题(详情版)

本文主要是介绍vue-element-admin解决三级目录的KeepAlive缓存问题(详情版),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

vue-element-admin解决三级目录的KeepAlive缓存问题(详情版)

本文章将从问题出现的角度看看KeepAlive的缓存问题,然后提出两种解决方法。本文章比较详细,如果只是看怎么解决,代码怎么改,请前往配置版。

一、解决问题之间,先理清问题出现的原因

①首先,观察一下“一级目录”和“二级目录”

通过vue devtools工具,截图如下。
可以看到,“一级目录”——Tab,可以缓存:
在这里插入图片描述

再看看,“二级目录”——DirectivePermission,也可以缓存:
在这里插入图片描述

可以发现,他们都在<App>——<Layout>——<AppMain>下,同时<KeepAlive>的include属性包含组件配置的“name”(如上面两图所示)。

②再看看,“三级目录”的情况

通过vue devtools工具,截图如下。
可以看到,“三级目录”——Menu1-1,不可以缓存:
在这里插入图片描述

看发现,其明细的不同,他在<App>——<Layout>——<AppMain>——<Menu1>下,比“一级目录”和“二级目录”多了<Menu1>。由于<KeepAlive>的include属性并不包含<Menu1>组件配置的name——“Menu1”,而是组件配置的name——“Menu1-1”(如下图),所有不缓存。更多<keep-alive>不缓存的原因,可以看个人的另一篇文章。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、解决问题

①添加<RouterViewKeepAlive>解决

这里,你可能想到。既然,“三级目录”不缓存的原因是“由于<KeepAlive>的include属性并不包含<Menu1>组件配置的name——‘Menu1’”。那我在的include属性上,始终包含“Menu1”就行了(网上已经早有人写过类似解决方法,本文章的该解决方法也是参考该文章——见该文章)。

  • 首先,添加<RouterViewKeepAlive>组件
    新目录:src\layout\components\RouterViewKeepAlive\RouterViewKeepAlive.vue

    <!-- 父级路由组件,用于二级路由上, 该二级可以被keep-alive缓存 -->
    <!-- 由于该二级可以被keep-alive缓存,所以其三级的内容将保存 -->
    <!-- 注意:面包屑关闭后,不会从KeepAlive的include属性清除 -->
    <template><div class="app-main"><router-view /></div>
    </template>
    <script>
    export default {name: 'RouterViewKeepAlive'
    }
    </script>
    <style lang="scss" scoped>.app-main {}
    </style>
    
  • 然后,在<AppMain>添加“cachedViews”计算属性上添加“RouterViewKeepAlive”
    目录:src\layout\components\AppMain.vue

    <script>
    export default {name: 'AppMain',computed: {cachedViews() {// return this.$store.state.tagsView.cachedViews// 加入RouterViewKeepAlive组件,总是缓存二级目录路由配置为“RouterViewKeepAlive”的return ['RouterViewKeepAlive', ...this.$store.state.tagsView.cachedViews]},key() {return this.$route.path}}
    }
    </script>
    
  • 最后,修改路由配置(以原项目Nested Routes路由配置nested.js为例)
    目录:src\router\modules\nested.js

    /** When your routing table is too long, you can split it into small modules **/import Layout from '@/layout'
    // 导入RouterViewKeepAlive
    import RouterViewKeepAlive from '@/layout/components/RouterViewKeepAlive/RouterViewKeepAlive.vue'const nestedRouter = {path: '/nested',component: Layout,redirect: '/nested/menu1/menu1-1',name: 'Nested',meta: {title: 'Nested Routes',icon: 'nested'},children: [{path: 'menu1',// component: () => import('@/views/nested/menu1/index'), // Parent router-viewcomponent: RouterViewKeepAlive, // 使用RouterViewKeepAlive作为二级组件// name: 'Menu1',name: 'RouterViewKeepAlive', // 名字改为“RouterViewKeepAlive”,虽然没必要,但为了维护性meta: { title: 'Menu 1' },redirect: '/nested/menu1/menu1-1',children: [{path: 'menu1-1',component: () => import('@/views/nested/menu1/menu1-1'),name: 'Menu1-1',meta: { title: 'Menu 1-1' }},{path: 'menu1-2',component: () => import('@/views/nested/menu1/menu1-2'),name: 'Menu1-2',redirect: '/nested/menu1/menu1-2/menu1-2-1',meta: { title: 'Menu 1-2' },children: [{path: 'menu1-2-1',component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),name: 'Menu1-2-1',meta: { title: 'Menu 1-2-1' }},{path: 'menu1-2-2',component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),name: 'Menu1-2-2',meta: { title: 'Menu 1-2-2' }}]},{path: 'menu1-3',component: () => import('@/views/nested/menu1/menu1-3'),name: 'Menu1-3',meta: { title: 'Menu 1-3' }}]},{path: 'menu2',name: 'Menu2',component: () => import('@/views/nested/menu2/index'),meta: { title: 'Menu 2' }}]
    }export default nestedRouter

    注意:由于配置了“component: RouterViewKeepAlive”,使用了<RouterViewKeepAlive>作为二级组件,替换了’@/views/nested/menu1/index’的<Menu1>组件。

  • 结果
    可以看到“三级或以上的目录”被成功缓存了
    在这里插入图片描述

注意:这种实现方式存在弊端,就是永远关不掉(TagsView上的关闭),会一直占用内存
在这里插入图片描述

②转变Router配置解决

现在方法一,存在“关闭面包屑却不会关闭该缓存,会一直占用内存”的弊端,那怎么解决呢?
那找一下关闭<keep-alive>缓存的方法,不就解决了吗?
由于vue-element-admin项目是通过<keep-alive>的include来完成的,include如果没有加上“RouterViewKeepAlive”,就会将所有的<RouterViewKeepAlive>没缓存,这不是我们想要的。我们想要的是“缓存对应key的<RouterViewKeepAlive>,然后移除对应key的<RouterViewKeepAlive>”。
但是,个人看了官网并未提供或暴露这种特殊的方法接口。

那现在我们换一种思路——“注册路由时,将三级或以上的路由配置转换为一级和二级的那样”,如下图:
在这里插入图片描述
由于vue-element-admin项目在左侧菜单栏等地方用到了@/store的permission.js的“routes”。所以,现在的思路是“只改变Router的挂载,其他保持不改”,步骤如下。

  • 首先,对permission.js,添加flattenRoutes方法和修改generateRoutes
    目录:src\store\modules\permission.js

    // ...
    /*** 将单个路由,假如有三级或三级目录,则转为二级目录格式* @param {Object} router 要处理的路由* @returns {Object} 处理后的路由*/
    function flattenRouter(router) {// 创建一个新的对象来存储转换后的路由const newRouter = {...router,children: []}const routerChildren = router.children// 从根路由开始扁平化if (routerChildren && routerChildren.length > 0) {flatten('', routerChildren)}/*** 递归函数来遍历和扁平化路由* @param {String} parentPath 父路由路径* @param {Array} routes 路由*/function flatten(parentPath, routes) {routes.forEach(route => {const { path, children } = route// 构建完整的路径const fullPath = `${parentPath}${path.startsWith('/') ? path.slice(1) : path}`// 如果当前路由有子路由,则递归处理if (children && children.length > 0) {flatten(`${fullPath}/`, children)} else {// 否则,将当前路由添加到新的children数组中newRouter.children.push({...route,path: fullPath})}})}return newRouter
    }/*** 处理路由,将三级或三级以上目录的转为二级目录格式* @param {Array} routes routes* @param {Array} 处理后的路由*/
    export function flattenRoutes(routes) {const res = []routes.forEach(route => {const newRouter = flattenRouter(route)res.push(newRouter)})return res
    }// ...
    const actions = {generateRoutes({ commit }, roles) {return new Promise(resolve => {let accessedRoutesif (roles.includes('admin')) {accessedRoutes = asyncRoutes || []} else {accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)}// 处理路由,将三级或三级以上目录的转为二级目录格式const flattenAccessedRoutes = flattenRoutes(accessedRoutes)// store存的是accessedRoutes,用于左侧边导航栏,多级目录(包含三级或以上)commit('SET_ROUTES', accessedRoutes)// resolve(accessedRoutes)// Promise的resolve出去的是flattenAccessedRoutes,用于路由,多级目录(已转换为二级目录格式,不包含三级或以上)resolve(flattenAccessedRoutes)})}
    }
    // ...
    

    完整代码如下:

    import { asyncRoutes, constantRoutes } from '@/router'/*** Use meta.role to determine if the current user has permission* @param roles* @param route*/
    function hasPermission(roles, route) {if (route.meta && route.meta.roles) {return roles.some(role => route.meta.roles.includes(role))} else {return true}
    }/*** Filter asynchronous routing tables by recursion* @param routes asyncRoutes* @param roles*/
    export function filterAsyncRoutes(routes, roles) {const res = []routes.forEach(route => {const tmp = { ...route }if (hasPermission(roles, tmp)) {if (tmp.children) {tmp.children = filterAsyncRoutes(tmp.children, roles)}res.push(tmp)}})return res
    }/*** 将单个路由,假如有三级或三级目录,则转为二级目录格式* @param {Object} router 要处理的路由* @returns {Object} 处理后的路由*/
    function flattenRouter(router) {// 创建一个新的对象来存储转换后的路由const newRouter = {...router,children: []}const routerChildren = router.children// 从根路由开始扁平化if (routerChildren && routerChildren.length > 0) {flatten('', routerChildren)}/*** 递归函数来遍历和扁平化路由* @param {String} parentPath 父路由路径* @param {Array} routes 路由*/function flatten(parentPath, routes) {routes.forEach(route => {const { path, children } = route// 构建完整的路径const fullPath = `${parentPath}${path.startsWith('/') ? path.slice(1) : path}`// 如果当前路由有子路由,则递归处理if (children && children.length > 0) {flatten(`${fullPath}/`, children)} else {// 否则,将当前路由添加到新的children数组中newRouter.children.push({...route,path: fullPath})}})}return newRouter
    }/*** 处理路由,将三级或三级以上目录的转为二级目录格式* @param {Array} routes routes* @param {Array} 处理后的路由*/
    export function flattenRoutes(routes) {const res = []routes.forEach(route => {const newRouter = flattenRouter(route)res.push(newRouter)})return res
    }const state = {routes: [],addRoutes: []
    }const mutations = {SET_ROUTES: (state, routes) => {state.addRoutes = routesstate.routes = constantRoutes.concat(routes)}
    }const actions = {generateRoutes({ commit }, roles) {return new Promise(resolve => {let accessedRoutesif (roles.includes('admin')) {accessedRoutes = asyncRoutes || []} else {accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)}// 处理路由,将三级或三级以上目录的转为二级目录格式const flattenAccessedRoutes = flattenRoutes(accessedRoutes)// store存的是accessedRoutes,用于左侧边导航栏,多级目录(包含三级或以上)commit('SET_ROUTES', accessedRoutes)// resolve(accessedRoutes)// Promise的resolve出去的是flattenAccessedRoutes,用于路由,多级目录(已转换为二级目录,不包含三级或以上)resolve(flattenAccessedRoutes)})}
    }export default {namespaced: true,state,mutations,actions
    }
    
  • 然后,进行测试
    更改文件如下:
    修改nested.js的name:‘Menu1-1’→’Menu11’。同理,‘Menu12’、‘Menu121’、‘Menu122’、‘Menu13’。
    目录:src\router\modules\nested.js

    /** When your routing table is too long, you can split it into small modules **/import Layout from '@/layout'const nestedRouter = {path: '/nested',component: Layout,redirect: '/nested/menu1/menu1-1',name: 'Nested',meta: {title: 'Nested Routes',icon: 'nested'},children: [{path: 'menu1',// component: () => import('@/views/nested/menu1/index'), // Parent router-viewname: 'Menu1',meta: { title: 'Menu 1' },redirect: '/nested/menu1/menu1-1',children: [{path: 'menu1-1',component: () => import('@/views/nested/menu1/menu1-1'),name: 'Menu11', // 已更改,便于测试meta: { title: 'Menu 1-1' }},{path: 'menu1-2',// component: () => import('@/views/nested/menu1/menu1-2'),name: 'Menu12', // 已更改,便于测试redirect: '/nested/menu1/menu1-2/menu1-2-1',meta: { title: 'Menu 1-2' },children: [{path: 'menu1-2-1',component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'),name: 'Menu121', // 已更改,便于测试meta: { title: 'Menu 1-2-1' }},{path: 'menu1-2-2',component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'),name: 'Menu122', // 已更改,便于测试meta: { title: 'Menu 1-2-2' }}]},{path: 'menu1-3',component: () => import('@/views/nested/menu1/menu1-3'),name: 'Menu13', // 已更改,便于测试meta: { title: 'Menu 1-3' }}]},{path: 'menu2',name: 'Menu2',component: () => import('@/views/nested/menu2/index'),meta: { title: 'Menu 2' }}]
    }export default nestedRouter
    

    修改‘menu1-1\index.vue’,添加和nested.js配置一样的name属性。同理,‘menu1\menu1-2\menu1-2-1\index.vue’、‘menu1\menu1-2\menu1-2-2\index.vue’、‘menu1\menu1-3\index.vue’(注意:同时,将开头的“<template functional>”改为“<template>”)。
    以menu1-1为例,代码如下,其他的同理。
    目录:src\views\nested\menu1\menu1-1\index.vue

    <template><div style="padding: 30px"><el-alert :closable="false" title="menu 1-1" type="success"><router-view /></el-alert></div>
    </template>
    <!-- 修改后 -->
    <script>
    export default {// 添加name属性,让keep-alive进行缓存name: 'Menu11'
    }
    </script>
    
  • 结果
    可以看到“三级或以上的目录”被成功缓存了
    在这里插入图片描述

注意事项:
①与方法一“添加<RouterViewKeepAlive>解决”的区别:
方法一是“使用了<RouterViewKeepAlive>作为二级组件,替换了’@/views/nested/menu1/index’的<Menu1>组件。”;
而方法二是“扁平化路由”,就如上方测试改nested.js那样,一些“component”的配置是没意义的,所以注释掉了
这种实现方式同样存在弊端,就是Breadcrumb 面包屑多级关系不见了。因为,由于vue-element-admin项目Breadcrumb 面包屑是通过$route来实现的,而我们恰好改的就是路由配置。区别如下:
在这里插入图片描述
在这里插入图片描述
③上面,添加flattenRoutes方法只是对“accessedRoutes”做了处理,还未对“constantRoutes”处理,比如同时对“constantRoutes”处理。处理代码如下:
目录:src\router\index.js

// ...
/* 处理路由 */
import { flattenRoutes } from '@/store/modules/permission'// ...
const createRouter = () => new Router({// mode: 'history', // require service supportscrollBehavior: () => ({ y: 0 }),// routes: constantRoutes// 处理路由,将三级或三级以上目录的转为二级目录格式routes: flattenRoutes(constantRoutes)
})
// ...

③直接移除include解决

此方法官方文档此次提到“前往@/layout/components/AppMain.vue文件下,移除include相关代码即可。当然直接使用 keep-alive 也是有弊端的,他并不能动态的删除缓存,你最多只能帮它设置一个最大缓存实例的个数 limit。

更改如下:
目录:src\layout\components\AppMain.vue

<template><section class="app-main"><transition name="fade-transform" mode="out-in"><!-- 移除include --><!-- <keep-alive :include="cachedViews"> --><keep-alive><router-view :key="key" /></keep-alive></transition></section>
</template>
// ...

如果,想要设置最大缓存个数,比如设置最大10个。只需将“<keep-alive>”改为“<keep-alive :max=“10”>”

注意事项:
①该方法的弊端:官方文档说的也很清楚了——“他并不能动态的删除缓存,只能帮它设置一个最大缓存实例的个数”。
②与方法一的对比:没使用了<RouterViewKeepAlive>作为二级组件,替换了’@/views/nested/menu1/index’的<Menu1>组件;该方法的“他并不能动态的删除缓存”的范围比方法一的范围大,该方法所有的目录都不能动态删除缓存,而方法一是三级或以上的目录不能移除。
③与方法二的对比:没“扁平化路由”无方法二的弊端——Breadcrumb 面包屑多级关系不见了

三、总结

vue-element-admin解决三级目录的KeepAlive缓存问题:
①添加<RouterViewKeepAlive>解决
弊端:永远关不掉(TagsView上的关闭),会一直占用内存
(可以给keep-alive设置一个最大缓存实例的个数,但不一定满足项目需求;如果该项目三级或以上的目录不多,就几个,那还能接受内存的占用)

②转变Router配置解决
弊端:Breadcrumb 面包屑多级关系不见了
(如果真实项目,无需“Breadcrumb 面包屑”同时最多三级(见方法二的“注意事项”),还可以接受。)

③直接移除include解决
弊端:他并不能动态的删除缓存,只能帮它设置一个最大缓存实例的个数
(如果真实项目,可以接受“不能动态的删除缓存”和“设置最大缓存实例的个数”的弊端,那该方法是最简单的解决方法。)

这里强调一下:由于上述方法是对原本vue-element-admin项目构建上的修复,一旦按照文章修复了,一定要记得项目的可维护性,不然,下一个接手该项目的码农将会很疑惑。比如,在真实项目的“README.md”上添加修改的文字描述和路由配置注意事项,同时在src\router\index.js的路由配置上注释好。

最终,似乎都没有十全十美的解决方案,每一种方案总是存在一些“舍去”。就vue-element-admin的作者在文档提过“如果没有标签导航栏需求的用户,建议移除此功能”。

网上也有更多的解决方法,比如:

  • 使用created解决
  • 设置hidden:true隐藏

如果想了解更多关于vue-element-admin项目<keep-alive>不缓存的原因,也欢迎看看个人的另一篇文章。
如果大家有其他更完美的解决方案或者本文章方法的不足之处,欢迎在评论区讨论!

四、参考文献

  • sweet202005——解决vue项目三级菜单路由无法缓存问题
  • Vue2——keep-alive

这篇关于vue-element-admin解决三级目录的KeepAlive缓存问题(详情版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1093045

相关文章

SQL Server配置管理器无法打开的四种解决方法

《SQLServer配置管理器无法打开的四种解决方法》本文总结了SQLServer配置管理器无法打开的四种解决方法,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录方法一:桌面图标进入方法二:运行窗口进入检查版本号对照表php方法三:查找文件路径方法四:检查 S

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

mysql中的数据目录用法及说明

《mysql中的数据目录用法及说明》:本文主要介绍mysql中的数据目录用法及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、版本3、数据目录4、总结1、背景安装mysql之后,在安装目录下会有一个data目录,我们创建的数据库、创建的表、插入的

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

Springboot如何正确使用AOP问题

《Springboot如何正确使用AOP问题》:本文主要介绍Springboot如何正确使用AOP问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录​一、AOP概念二、切点表达式​execution表达式案例三、AOP通知四、springboot中使用AOP导出

Python中Tensorflow无法调用GPU问题的解决方法

《Python中Tensorflow无法调用GPU问题的解决方法》文章详解如何解决TensorFlow在Windows无法识别GPU的问题,需降级至2.10版本,安装匹配CUDA11.2和cuDNN... 当用以下代码查看GPU数量时,gpuspython返回的是一个空列表,说明tensorflow没有找到

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM

XML重复查询一条Sql语句的解决方法

《XML重复查询一条Sql语句的解决方法》文章分析了XML重复查询与日志失效问题,指出因DTO缺少@Data注解导致日志无法格式化、空指针风险及参数穿透,进而引发性能灾难,解决方案为在Controll... 目录一、核心问题:从SQL重复执行到日志失效二、根因剖析:DTO断裂引发的级联故障三、解决方案:修复