Vue缓存策略
页面样式
需求1
从详情页返回列表页,需记录列表页的选择状态和页码
方案:使用localstorage或vuex实现
注:示例代码和业务可能存在依赖,这里只是提供思路
前提:列表页需要实现分页组件
首先在设置一个开关:openroute,实现灰度上线
props: {
openroute: {
type: Boolean,
default: () => (false)
}
}
在分页组件中存储选择条件和页码
let newPage;
//openroute处理
if (this.openroute) {
//缓存page
if (page) { //page存在则缓存page
localStorage.setItem("lastPath", location.href + "@" + page);
} else {
let lastPath = localStorage.getItem("lastPath");
if (lastPath && lastPath !== 'undefined' && lastPath.split('@')
&& lastPath.split('@')[0] === location.href) { //同一页面时的处理逻辑
let condition = localStorage.getItem("condition");
if (condition !== JSON.stringify(params.data)) { //选择条件变更则重置page
localStorage.setItem("lastPath", location.href + "@1");
} //选择条件未变更则保持page
} else { //未存储过数据 或者 页面地址发生变更 时则重置page
localStorage.setItem("lastPath", location.href + "@1");
}
}
//缓存condition
localStorage.setItem("condition", JSON.stringify(params.data));
//获取page
let lastPath = localStorage.getItem("lastPath");
if (lastPath && lastPath !== 'undefined' && lastPath.split('@') && lastPath.split('@')[1]) {
newPage = Number(lastPath.split('@')[1]);
} else {
newPage = 1;
}
} else {
newPage = page;
}
在列表页面打开开关,并且在created方法中读取存储的数据
u-pagination(@paginated="afterPagination", :params="pagination", :immediate.sync="pagination_immediate" :openroute="true")
//获取缓存的数据
let condition = localStorage.getItem("condition");
if (condition) {
Object.assign(this.form, JSON.parse(condition))
}
存在的问题
如果请求参数和template中显示的字段不同,会增加回显的工作量。
需求2
由A进入B页面,若点击返回按钮回到A页面,需要缓存A页面;若点击菜单栏回到A页面,不需要缓存A页面。
方案:使用keepalive实现
注:示例代码和业务可能存在依赖,这里只是提供思路
结合include缓存页面
<template>
<keep-alive include="KeepAlive">
<router-view :key="key"></router-view>
</keep-alive>
</template>
<script>
export default {
computed: {
key () {
return this.$route.fullPath
}
}
}
</script>
在业务页面中增加name: 'KeepAlive',表示该页面应缓存;配合使用activated和deactivated实现一些必要操作
<script>
export default {
name: 'KeepAlive',
activated () {
if (!this.isCreated) {
// 执行页面刷新操作
}
this.isCreated = false
},
data () {
return {
isCreated: false,
}
},
created () {
this.isCreated = true
},
}
</script>
如果执行了页面刷新操作,可能会无法保存页码,可以通过以下方式获取正确的页码
let newPage = page
// KeepAlive缓存处理
if (this.$parent.$vnode.tag.indexOf('KeepAlive') !== -1) {
// 缓存page
if (page) { // page存在则缓存page
localStorage.setItem('lastPath', location.href + '@' + page)
} else {
let lastPath = localStorage.getItem('lastPath')
if (lastPath && lastPath !== 'undefined' && lastPath.split('@') &&
lastPath.split('@')[0] === location.href) { // 同一页面时的处理逻辑
let condition = localStorage.getItem('condition')
let newData = params.data
delete newData.page
delete newData.size
if (condition !== JSON.stringify(newData)) { // 选择条件变更则重置page
localStorage.setItem('lastPath', location.href + '@1')
} // 选择条件未变更则保持page
} else { // 未存储过数据 或者 页面地址发生变更 时则重置page
localStorage.setItem('lastPath', location.href + '@1')
}
}
// 缓存condition
let cache = params.data
delete cache.page
delete cache.size
localStorage.setItem('condition', JSON.stringify(cache))
// 获取page
let lastPath = localStorage.getItem('lastPath')
if (lastPath && lastPath !== 'undefined' && lastPath.split('@') && lastPath.split('@')[1]) {
newPage = Number(lastPath.split('@')[1])
} else {
newPage = 1
}
} else {
newPage = page
}
Vue不提供删除keepalive缓存的逻辑,可以通过以下代码实现
在main.js中注入全局方法
import mixins from './utils/mixins'
Vue.mixin(mixins)
在mixins.js中添加删除keepalive缓存的逻辑
export default {
beforeRouteLeave (to, from, next) { // keep-alive缓存策略,this是from页面的实例
try {
if (from && from.params && from.params.back) { // 如果是返回到to页面,则不清除to页面的缓存
next()
return
} // 如果是 点击菜单栏 或者 修改地址栏 或者 点击浏览器的前进后退按钮 进入到to页面,则需要清除to页面的缓存
if (to && to.meta.rank && from && from.meta.rank && to.meta.rank === from.meta.rank && // 规定菜单栏点击出现的页面rank为3,其它页面rank为4
this.$vnode && this.$vnode.data.keepAlive && // KeepAlive为true
this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache && // 有缓存
this.$vnode.componentOptions // 有配置项
) { // 判断是否符合清除to页面缓存的条件
const cache = this.$vnode.parent.componentInstance.cache
const keys = this.$vnode.parent.componentInstance.keys
const key = to.fullPath
if (key && cache[key]) {
if (keys.length) { // 清除keys数组中的key数据
const index = keys.indexOf(key)
if (index > -1) {
keys.splice(index, 1)
}
}
delete cache[key] // 清除指定key的缓存
}
}
} catch (e) {
console.log(e)
} finally {
next()
}
}
}
存在的问题
如果页面中存在组件或全局监听事件(一般都会存在),可能会导致难以解决的问题。
需求2 - 【目前使用方案】
由A进入B页面,若点击返回按钮回到A页面,需要缓存A页面;若点击菜单栏回到A页面,不需要缓存A页面。
方案:使用vue-router+vue-extends实现
注:示例代码和业务可能存在依赖,这里只是提供思路
在router配置文件中将能通过菜单栏点击出现的页面设置rank=3,其它(如各详情页)为4
因为目前只需为rank为3的页面设置缓存
创建基类BaseCache.vue,并为要缓存的页面实现继承关系
import BaseCache from '@/BaseCache'
export default {
extends: BaseCache,
...
}
在beforeRouteLeave(to, from, next)中按顺序执行以下3步逻辑
- 缓存from页面的data到window.cacheData
- 判断是否是返回到上一页面,是则return,不执行第3步
- 判断rank是否相同,相同则清除to页面的缓存
具体代码如下
beforeRouteLeave (to, from, next) { // 配合缓存策略,this是from页面的实例
try {
// 规定菜单栏点击出现的页面rank为3,其它页面rank为4
if (to && to.meta.rank && from && from.meta.rank) { // 过滤未设置rank的页面
// rank为3的页面才设置缓存数据
if (this.$data && from.meta.rank === 3) {
let cacheData = {}
// 读取缓存数据
if (window.cacheData) {
cacheData = window.cacheData
}
// 写入缓存数据
cacheData[from.path] = this.$data
window.cacheData = cacheData
}
// 如果是返回到to页面,则不清除to页面的缓存
if (from && from.params && from.params.back) {
next()
return
}
if (to.meta.rank === from.meta.rank) { // rank相同则清除缓存,说明是点击操作
let cacheData = {}
// 读取缓存数据
if (window.cacheData) {
cacheData = window.cacheData
}
// 如果是同一级页面则to页面的清除to页面的缓存,但不关心删除结果
delete cacheData[to.path]
window.cacheData = cacheData
}
}
} finally {
// 保证一定执行next()方法
next()
}
}
在基类中的created方法中读取缓存并回显 & 监听F5刷新操作
具体代码如下:
<template>
<div></div>
</template>
<script>
export default {
name: 'BaseCache',
mounted () {
window.onbeforeunload = () => {
// 刷新页面则清除所有缓存
window.cacheData = {}
}
},
created () {
let cacheData = {}
// 读取缓存数据
if (window.cacheData) {
cacheData = window.cacheData
}
Object.assign(this.$data, cacheData[this.$route.path])
}
}
</script>
最后为了减少内存支出,我们会在登出之后清除所有缓存
存在问题
- 如果要缓存的页面存在
父子组件
关系,目前无法实现缓存; - 如果因缓存数据量过大,导致了性能问题,还需要想一个好的策略。