零、先说重点
一年后重读此文章深感其中逻辑理解起来不够简单。还是决定先把关键点说清楚,后面的即使不看也能自由发挥。
可以实现的场景:
- 锁屏页面:缓存上一次路由
- 详情页面:缓存所属列表页面;其他页面跳转此页面均不缓存。
- 主页面:均不缓存
1.利用keep-alive的include属性实现动态缓存
- 以下为缓存的三种情况
// 1.不缓存
<keep-alive :include="[]"></keep-alive>
// 2.缓存所有页面
<keep-alive :include="/\.*/"></keep-alive>
// 3.缓存部分页面
<keep-alive :include="['pageA', pageB]"></keep-alive>
2.利用vue-router的路由守卫动态生成缓存规则
- beforeEach:根据from,to生成。存储到vuex中。
let router = new Router({...});
router.beforeEach((to, from, next) => {
setKeepAlive(from, to);
setTimeout(_ => {
next();
}, 200);
});
function setKeepAlive(from, to) {
// 1.你的逻辑
// 2.存储到vuex中
}
- 路由配置中存储生成依据
{
path: 'A',
name: 'A',
meta: {
title: '列表页1',
逻辑中用到的变量: '',
},
component: resolve => require(['../page/keepalive/A.vue'], resolve)
}
- 注意要做from判断,首次进入页面和刷新时,均为null
重点已说完,以下为具体实现案例:
一、解决场景
现有页面:
- 一层:登陆、主页、锁屏
- 二层(主页下):列表页1、列表页2、详情页2、我的信息
达到效果
一层:
- 主页<–>锁屏(缓存主页/主页中组件内容,不缓存锁屏)
- 主页<–>登陆(不缓存)
二层:
- 详情2 <–> 列表2(缓存列表2)
- 列表1 --> 列表2(不缓存);列表1 --> 同层任意(缓存自身);
- 我的信息 --> 同层任意(缓存其他同级,不缓存自身)
二、实现
思路
配置路由信息 --> 路由时确定缓存列表 --> keep-alive中include实时获取,达到动态效果
1.配置路由信息(router/index.js)
- 页面分类一:缓存自身,有例外
{
path: 'A',
name: 'A',
meta: {
title: '列表页1',
requireAuth: false,
keepAlive_self: true, // 缓存自身
keepAlive_excep: ['B'] // 当与B(列表页2)切换时不缓存
},
component: resolve => require(['../page/keepalive/A.vue'], resolve)
}
- 不缓存自身,有例外
{
path: 'B',
name: 'B',
meta: {
title: '列表页2',
requireAuth: false,
keepAlive_self: false, // 不缓存自身
keepAlive_excep: ['Bb'] // 当与Bb(详情页2)切换,缓存自身
},
component: resolve => require(['../page/keepalive/B.vue'], resolve)
}
- 缓存其他所有同级页面
{
path: 'C',
name: 'C',
meta: {
title: '缓存组件c',
requireAuth: false,
keepAlive_other: true // 二级页面切换时,缓存
},
component: resolve => require(['../page/keepalive/C.vue'], resolve)
},
]
}
- 缓存所有页面
{
path: '/lock',
name: 'lock',
meta: {
title: '锁屏',
requireAuth: false,
g_keepAlive_other: true // 缓存所有页面
},
component: resolve => require(['../page/keepalive/lock.vue'], resolve)
}
2.配置时生成缓存列表(router/index.js)
import Vue from 'vue'
import Router from 'vue-router'
import store from '../store/store'
Vue.use(Router);
let router = new Router({...});
router.beforeEach((to, from, next) => {
setKeepAlive(from, to);
setTimeout(_ => {
next();
}, 200);
});
function setKeepAlive(from, to) {
console.log('from,to:', from, to);
// 设置状态可在vuex中简化代码,注意规则名称与组建name有关(并非路由中的name)
if (!from.name) { // 如果没有上个页面信息(首次访问或刷新页面)
console.log('如果没有上个页面信息(首次访问或刷新页面)');
return null;
} else if (from.meta.g_keepAlive_other || to.meta.g_keepAlive_other) { // 缓存所有一级以下路由:读取/储存
console.log('缓存所有:读取/储存');
store.commit('keepAlive', /\.*/); // 缓存所有二级
store.commit('gKeepAlive', /\.*/); // 缓存所有一级
} else if (from.meta.keepAlive_other || to.meta.keepAlive_other) { // 缓存其他页面:读取/储存
console.log('缓存其他页面:读取/储存');
store.commit('gKeepAlive', []); // 不缓存一级
store.commit('keepAlive', /\.*/); // 缓存所有二级
return null;
} else {
console.log('普通跳转:from与to分别只影响自身');
// from:是否需要存储缓存
let keppSelfF = from.meta.keepAlive_self || false;
let excepListF = from.meta.keepAlive_excep || []; // 读取例外条件
let isInF = excepListF.indexOf(to.name) > -1;
let hasWrite = keppSelfF !== isInF; // 判断是否有例外
// to:是否需要读取缓存
let keppSelfT = to.meta.keepAlive_self || false;
let excepListT = to.meta.keepAlive_excep || []; // 读取例外条件
let isInT = excepListT.indexOf(from.name) > -1;
let hasRead = keppSelfT !== isInT; // 判断是否有例外
// 生成新缓存规则
let keepAlive = [];
if (hasWrite) {
keepAlive.push(from.name);
}
if (hasRead) {
keepAlive.push(to.name);
}
store.commit('gKeepAlive', []); // 不缓存一级一面
store.commit('keepAlive', keepAlive); // 缓存所有二级页面
}
}
3.vuex中state
gKeepAlive: [], // 全局默认不缓存默认不缓存
keepAlive: [] // 局部,默认不缓存
4.keep-alive组件动态读取
<!-- index.vue 一层 -->
<keep-alive :include="$store.getters.gKeepAlive">-->
<router-view/>
</keep-alive>
<!-- main.vue 二层 -->
<keep-alive :include="$store.getters.keepAlive">
<router-view/>
</keep-alive>
5.缓存其他页面,但自身不缓存,离开前需要销毁自身缓存信息
// 防止缓存
beforeRouteLeave(to, from ,next) {
this.$destroy();// 销毁缓存
next();
}
其他
- 存在问题
- 如果多层嵌套可能要多个vuex变量控制。
- 尝试过方式
期间使用过切换的方式,弊端是已渲染的页面无法动态设置缓存。
而且from和to要分开判断,(存储与读取缓存要分开)
试图动态设置路由配置的meta信息,发现实时性不好,而且bug多。
<keep-alive>-->
<router-view v-if="$store.keep"/>
</keep-alive>
<router-view v-if="!$store.keep"/>