vue+vuerouter+vuex通过keepalive控制页面缓存

需求:

vue+vuerouter+vuex通过keepalive控制页面缓存,有时候我们从列表页A进入详情页的时候,而详情页里面也有一个列表页B,当我们从详情页进入列表页B时,希望详情页缓存下来,当从列表页B返回到详情页时,我们需要详情页,不需要重新请求渲染页面,并且当前页面的滚动位置回到原来的位置,再继续返回到列表页A时,我们需要详情页不被缓存

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
project   

└───src
│ │
| └───mixins
| | scroll-position.js
| |
│ └───store
| | | index.js
| | |
| └───common
| index.js
| |
│ └───common
| tools.js
| |
| └───router
| | index.js
| |
| └───views
| | list-a.vue
| | detail.vue
| | list-b.vue
| |
| └───App.vue

1.编写保存滚动位置方法

在目录下新建一个mixins文件夹,在目录下新建一个scroll-position.js,用户保存离开页面的滚动位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const scrollPosition = {
data() {
return {
}
},
activated() {
if (this.$route.meta.keepAlive) {
let pageName = this.$route.name;
let pagePositions = this.$store.state.CommonModule.pagePositions;
let res = pagePositions.find(v => v.name === pageName);
if (res && res.position > 0) {
this.$nextTick(function () {
document.documentElement.scrollTop = document.body.scrollTop = res.position || 0;
});
}
}
},
computed: {}
};

export default scrollPosition;

2.编写状态管理

在目录下新建一个store文件夹,在store文件夹下面新建一个index.js,在store文件夹下面新建一个common文件夹,在common文件夹下面新建一个index.js

/common/tools.js

1
2
3
4
5
6
7
8
/**
* 获取数据类型,返回结果为 Number、String、Object、Array等
* @param value
* @returns {string}
*/
export const getRawType = (value) => {
return Object.prototype.toString.call(value).slice(8, -1)
};

/store/common/tools.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import {getRawType} from '../../common/tools';

const state = {
pagePositions: [],
cachedPages: []
};

const mutations = {
SAVE_POSITION(state, data) {
let pagePositions = state.pagePositions;
let key = -1;
pagePositions.find((v, k) => {
if (v.name === data.name) {
key = k;
return v
}
});
key >= 0 ? pagePositions[key] = data : pagePositions.push(data);
state.pagePositions = pagePositions;
},
SET_CACHED_PAGE(state, pageName) {
if (getRawType(pageName) === 'Array') {
state.cachedPages = pageName
} else {
let cached_pages = state.cachedPages;
let res = cached_pages.filter(cachedPage => cachedPage === `page-${pageName}`);
if (res.length <= 0) {
cached_pages.push(`page-${pageName}`);
state.cachedPages = cached_pages;
}
}
},
REMOVE_CACHED_PAGE(state, pageName) {
let cached_pages = state.cachedPages;
let index = cached_pages.findIndex(cachedPage => cachedPage === `page-${pageName}`)
if (index >= 0) {
cached_pages.splice(index, 1);
state.cachedPages = cached_pages
}
}
};

const getters = {
};

const actions = {
savePosition({commit}, data) {
commit('SAVE_POSITION', data)
},
setCachedPage({commit}, pageName) {
commit('SET_CACHED_PAGE', pageName)
},
removeCachedPage({commit}, pageName) {
commit('REMOVE_CACHED_PAGE', pageName)
}
};

export default {
namespace: true, //这里使用了vuex的命名空间,方便管理
state,
mutations,
getters,
actions
}

/store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
import Vue from 'vue'
import Vuex from 'vuex'
import CommonModule from './common'

Vue.use(Vuex);

export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production', //严格模式
modules: {
CommonModule
}
})

3.对特定页面编写缓存方法

app.vue

利用keepliveinclude属性,详情可看https://cn.vuejs.org/v2/api/#keep-alive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div id="app">
<div class="app-container">
<keep-alive :include="cachedPages">
<router-view/>
</keep-alive>
</div>
</div>
</template>

<script type="text/ecmascript-6">
import {mapState} from 'vuex';

export default {
data() {
return {}
},
components: {
},
created() {
},
computed: {
...mapState({
cachedPages: state => state.CommonModule.cachedPages
})
}
}
</script>
<style lang="less">
</style>

/views/lista.vue

详情页面加入beforeRouteLeave生命周期,
缓存的页面再次被激活的时候会触发activated生命周期,页面被缓存离开的时候会触发deactivated生命周期

/views/lista.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div>
i am list a
</div>
</template>

<script type="text/ecmascript-6">
import scrollPosition from "../mixins/scroll-position";

export default {
name: 'page-list-a',
mixins: [scrollPosition],
data() {
return {
}
},
beforeRouteLeave(to, from, next) {
if (['detail'].some(name => name === to.name)) {
from.meta.keepAlive = true;
this.$store.dispatch('setCachedPage', from.name).then(() => {
next()
});
} else {
from.meta.keepAlive = false;
this.$store.dispatch('removeCachedPage', from.name).then(() => {
next();
});
}
}
};
</script>
<style lang="less" scoped>
</style>

/views/detail.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div>
i am detail
</div>
</template>

<script type="text/ecmascript-6">
export default {
name: 'page-detail',
data() {
return {
}
},
beforeRouteLeave(to, from, next) {
if (['list-a'].some(name => name === to.name)) {
from.meta.keepAlive = true;
this.$store.dispatch('setCachedPage', from.name).then(() => {
next()
});
} else {
from.meta.keepAlive = false;
this.$store.dispatch('removeCachedPage', from.name).then(() => {
next();
});
}
}
};
</script>
<style lang="less" scoped>
</style>

list-b.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div>
i am list b
</div>
</template>

<script type="text/ecmascript-6">

export default {
name: 'page-list-b',
data() {
}
</script>
<style lang="less" scoped>
</style>

/router/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import Vue from 'vue'
import VueRouter from 'vue-router'

// 重写新版VueRouter的push和replace方法的异常捕捉
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject);
return originalPush.call(this, location).catch(err => err)
};
Vue.use(VueRouter);

const routes = [
{
path: '/list-a',
name: 'list-a',
meta: {
title: '列表页A'
},
component: () => import ('../views/list-a')
},
{
path: '/detail',
name: 'detail',
meta: {
title: '详情页',
keepAlive: true
},
component: () => import ('../views/detail')
},
{
path: '/list-b',
name: 'list-b',
meta: {
title: '列表页B'
},
component: () => import ('../views/list-b')
}
];

const router = new VueRouter({
mode: 'history',
routes
});

export default router