1 问题描述
自从使用iview-admin这个框架以来,长久有一个问题始终困扰着我,就是每次载入新路由(vue-router)时,总是会重新读取请求数据,这就引出了两个很严重的问题:
- 请求重复,响应速度慢(前者一般是后者的原因);
- 每次切换路由时,组件中的data都会重置(这在逻辑上很要命,譬如开销售开单时,需要选择批次,这时你需要到进销存页面查询实际各仓库各批次的数据,查询后再返回时,会很郁闷的发现之前填写的数据都没了,等你再重写填写单据到批次选择那里时,很可能批次数据又忘了,本来应该提供方便的系统,反而造成了不便);
如何使组件内的data形成缓存保留下来,在开发这两三个月以来,始终是悬挂在我心头上的一个大问题.在各种搜索问题的结果中,最接近的答案似乎就是使用keep-alive,但实际使用并不顺利,中间遇到了各种各样的问题(这些问题,我会在第3点中加以说明),但总算得到了比较满意的结果.
2 解决方案
实际的解决方案,只有核心的两点:
- 修改iview-admin中的App.vue,代码如下(这里只列举相关代码):
<template>
<div id="app">
<keep-alive>
<router-view :key="getRouteKey" v-if="$route.meta.keepAlive" ></router-view>
</keep-alive>
<router-view :key="getRouteKey" v-if="!$route.meta.keepAlive" >
<!-- 这里是不被缓存的视图组件,比如 Edit! -->
</router-view>
</div>
</template>
<script>
import { saveStoreState, loadStoreState } from '@/store/data-manage'
export default {
name: 'App',
computed: {
getRouteKey () {
// 当前路由的唯一key
return this.$route.name + (this.$route.params.id || '')
}
},
created () {
//此处代码与本文主题无关
}
}
</script>
- 对router.js中需要被缓存的路由组件加上keepAlive:true,如下:
{
path: 'ziliao-shangpin',
name: 'shangpin',
meta: {
access: ['super_admin', 'admin', 'shangpin'],
icon: 'md-funnel',
title: '商品',
//这里是要添加的属性,如果不设置,则默认为false
keepAlive: true,
},
component: () => import('@/view/basic-data/product/product.vue')
},
关于以上两点,有以下说明:
- <keep-alive>是控制缓存数据的核心,这是vue自带的设置,基础知识请参考这里.
- 如果你直接使用<keep-alive>去包裹<router-view>,那么大概率会什么缓存都体现不到,这里的原因比较复杂,但关键就是需要设置key.我所在的项目将key设置为组件的name加参数中的id,如果有其他需求,也可对此进行额外的修改;
3 常见问题说明
这里主要将一些我在解决问题过程中遇到的各种各种的问题.
- 很多博文中,即使<keep-alive>中包裹的<router-view>没有设置key,也能正常的缓存,这是什么原因?
答: 仔细看那些博文代码中component后面跟的内容,都是一个个具体的组件,而非像iview-admin这样后面跟着的,是一个函数式组件.
以下是我的推测,在没有设定key的时候,keep-alive形成缓存是以具体组件的name为区别的.
而都是函数式组件(且没有key)的情况下,就只能形成一个缓存区.
譬如有三个路由组件a,b,c,其中a,b的keepAlive为true,c的为false.
跳转方式 | 结果 |
---|---|
a->c->a或b->c->b | 留下缓存 |
a->b或b->a | 不会留下缓存(没来及留下) |
a->c->b或b->c->a | 会留下缓存,但留下的缓存是一模一样的 |
关于第3行,部分人可能不明白,稍微展开解释下,譬如a中data的someNum设为123后,以这种跳转方式跳转到b中,将b中data同名的someNum设为456,再以这种方式返回到a中,就会发现a中的someNum也变成了456, 这就证明了这种跳转方式,是建立在a和b共享缓存数据的基础上的.这样显然不符合逻辑要求. |
当设置key后,就相当于设置了不同的缓存分区,使得keep-alive的缓存按照key的方式分开;
- key的设置方式,为何不能直接设为$route.name?
这是因为当切换组件回来时,如果判定有缓存数据,组件就不会被重新创建,这不符合一种常见的逻辑,即从列表中跳转到单据主页面的组件,如果是两个不同的单据,显示内容应当不一致,不应当使用相同的缓存数据,所以这里再加上id借以区分;
4 更严谨的逻辑
以上的解决方案,只是解决了较为基础的情况,但有一些较为特殊的情况,需要区分何时使用缓存,何时不使用.总共有两大类:
- 其他人和前端操作导致数据库变化;
->仅凭前台必定无法监测到这类变化,要自动化实现,则需后台参与,相对来说比较困难和繁琐,一般建议是通过前台的刷新来实现; - 一个组件的数据操作,影响到另一个组件的数据(仅考虑路由组件即可);
->理论上前台都可监测到,问题在于如何实现?
->个人认为比较好的设计方案,是通过一级链接网(这是一个自定义的新名词)实现;
一级链接网简要介绍(仅逻辑,技术上可考虑存储到vuex的store中):
- 将每个route的name,链接到若干关联的routeName集合中,形成类似键值对的一级关系网;
- 当作为键值对中的键所在路由发生变化时,查询出对应值的所在路由,发出信号清除其缓存数据;
- 但仅影响到一级,不会继续呈树状一样的往下传递,否则可能会形成往复链,或者链接路由数据过多(这种就需要递归了),在效率和逻辑上都是不理想的.
关于使keep-alive失效的技术手段;
- 刷新或模拟刷新,弊端是会使所有路由的缓存数据都无效化;
- 将created的执行的方法,放在activated中,根据一键连接网判断是否需要执行;
- [待测试]同样利用一级连接网,直接令router.js中的keepAlive为false,加载完毕后再变更为true;