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

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)