解决什么问题?
在项目开发中遇到了这样一种需求:从某个页面跳转到另一个页面,再跳转回来时,应当保持跳转前的样子。
比如,用户在某个列表做了一番筛选,点击某个筛选页查看了详情,跳转回列表页时希望筛选条件和结果依然保留着。
对此,我们一般有几个方案:
-
数据回填。当跳转到新页面时将旧页面需要存留的数据带过去,等跳转回来时再填在页面上。
-
使用全局变量、vuex等,将旧页面的数据进行状态管理,页面返回时填上。
-
不跳转到新页,只是在原来的页上覆盖一个 弹层或者隐藏原来的组件,显示新页内容。
以上三种方案中,第一种方案对每一种情况都要做自己的处理,且要把数据传来传去,繁琐且容易出错;第二种方案虽然不需要自行传递数据,但对状态的管理也是一大麻烦事,何时存储、何时清除、何时回填,都要考虑,另外也像第一个方案一样,和业务紧紧耦合。
那么,看来第三种方案就是最优方案啦,不用关心数据的存来存去,也很少关心业务,只需把原来的组件隐藏就好,因为只是隐藏状态自然不会丢失。
但是!我又遇到了第二个问题:浏览器上的刷新与回退。
诚然,第三种方案简单便捷,但是有一种用户常用习惯不能忽视:点击浏览器的刷新与回退按钮。
当使用第三种方案时,本质上没有开启新页面,只是在把旧内容隐藏、展示新内容而已。可是用户不会管是不是实质新页面,看起来是另一个页面,那么当点击刷新时,就应该在新内容上刷新,谁知却退到了上一处,点击回退希望只退回一层,却退回了上上层。
比如这个 bug…
1.进入页面A,点击按钮,进入页面B,点击浏览器的刷新按钮
【实际结果】
退出了页面B,回到了页面A
【预期结果】
停留在页面B
2.进入页面A,点击按钮,进入页面B,点击浏览器的回退按钮
【实际结果】
退出了页面B,跳转到不相关的页面C
【预期结果】
回到页面A
于是,我们找到了 keep-alive .
怎么做?

keep-alive 解决了缓存组件的问题,随后我们惊喜的发现,路由级的组件也可以缓存。但这东西也多多少少有点坑。
-
当在路由级组件上使用 keep-alive 时,其上层必须还有一层路由。
-
不能很容易的清除缓存。
经过一番探索发现,虽然直接清除缓存是不容易的,但可以通过 keep-alive 的 include 限定缓存哪些组件,将需要缓存的组件名置于 include 中,需要清除缓存就将该名字从数组中删掉。
而更细致的清除与保存时机可以通过路由守卫实现——在配置路由时meta中放入判断函数,路由守卫中调用函数判断是否清理或增加缓存。
实现
对原本的路由做一层包装,使要缓存的路由皆成为子路由,在他们的父路由中设置路由守卫,调用 meta 中的 cache 函数管理缓存。这样一来,极大降低了状态存留的成本 —— 除了对缓存时机的判断与业务相关写在路由配置里,其他操作均解耦于业务。
路由包装函数
// cacheRoutes.js
const routerComponent = {
template: `
<template>
<keep-alive :include="cacheArr">
<router-view />
</keep-alive>
</template>
`,
data() {
return {
cacheArr: [],
lastCacheRoute: {
to: '',
from: '',
}
}
},
methods: {
cleanCache(nto, nfrom) {
const {to, from} = this.lastCacheRoute;
if (to !== nfrom || from !== nto) {
this.cacheArr = [];
this.lastCacheRoute = {
to: '',
from: '',
};
}
},
changeCache(to, from) {
if (from.meta && from.meta.cache) {
const cacheName = from.meta.cache(to, from);
this.cacheArr = cacheName ? [cacheName]: [];
this.lastCacheRoute = {
to: to.name,
from: from.name,
};
}
}
},
created() {
this.$router.beforeEach((to, from, next) => {
this.cleanCache(to.name, from.name);
this.changeCache(to, from);
next();
});
},
};
export cacheRoutes(routes) {
return [
{
path: '',
component: routerComponent,
children: routes,
meta: {},
redirect: routes[0] && routes[0].path || undefined
}
];
}
注意: 只适用于单层级的缓存,比如 A->B 中缓存了 A,但如果接下来路由没有返回 A(即 B->C ),A 的缓存将被删除
引用方法
import { cacheRoutes } from 'xxx/cacheRoutes.js';
...
let routes = [...] // 原本的 routes
routes = cacheRoutes(routes);
...
注明缓存时机
// 原本的 routes
routes = [
...
{
path: '/test',
component: () => import('@/views/pages/test/index.vue'),
name: 'test',
meta: {},
},
...
];
// 注明缓存时机的 routes
routes = [
...
{
path: '/test',
component: () => import('@/views/pages/test/index.vue'),
name: 'test',
meta: {
cache: (to, from) => { // 去往路由的 route, 当前路由的 route
const {name, query} = to;
if (name === 'test' && query.type === 'view') { // 当需要缓存时,返回需缓存的组件名,不支持匿名组件
return 'test';
}
return ''; // 否则请返回空字符串
}
},
},
...
];
另:钩子函数 activated,当被 keep-alive 缓存的组件激活时调用,可以用于回到被缓存页面时局部刷新数据。
该钩子在服务器端渲染期间不被调用。
------------------------ E -------------- N ------------------- D ------------------------
参考资料: