vlambda博客
学习文章列表

vue+vuerouter+vuex+keepalive动态缓存页面

需求:

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

目录结构

 1project   
2
3└───src
4│   │
5|   └───mixins
6|
   |        scroll-position.js
7|
   |   
8│   └───store
9|
   |   |    index.js
10|   |   |    
11│   |
   └───common
12│   |            index.js 
13|
   |   
14│   └───common
15│   |
          tools.js
16|   |    
17|   └───router
18|
   |           index.js
19|
   |
20|
   └───views
21|   |        list-a.vue
22|   |        detail.vue
23|   |        list-b.vue
24|   |
25|   └───App.vue
26

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

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

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

2.编写状态管理

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

/common/tools.js

1/**
2 * 获取数据类型,返回结果为 Number、String、Object、Array等
3 * @param value
4 * @returns {string}
5 */

6export const getRawType = (value) => {
7    return Object.prototype.toString.call(value).slice(8-1)
8};

/store/common/tools.js

 1import {getRawType} from '../../common/tools';
2
3const state = {
4    pagePositions: [],
5    cachedPages: []
6};
7
8const mutations = {
9    SAVE_POSITION(state, data) {
10        let pagePositions = state.pagePositions;
11        let key = -1;
12        pagePositions.find((v, k) => {
13            if (v.name === data.name) {
14                key = k;
15                return v
16            }
17        });
18        key >= 0 ? pagePositions[key] = data : pagePositions.push(data);
19        state.pagePositions = pagePositions;
20    },
21    SET_CACHED_PAGE(state, pageName) {
22        if (getRawType(pageName) === 'Array') {
23            state.cachedPages = pageName
24        } else {
25            let cached_pages = state.cachedPages;
26            let res = cached_pages.filter(cachedPage => cachedPage === `page-${pageName}`);
27            if (res.length <= 0) {
28                cached_pages.push(`page-${pageName}`);
29                state.cachedPages = cached_pages;
30            }
31        }
32    },
33    REMOVE_CACHED_PAGE(state, pageName) {
34        let cached_pages = state.cachedPages;
35        let index = cached_pages.findIndex(cachedPage => cachedPage === `page-${pageName}`)
36        if (index >= 0) {
37            cached_pages.splice(index, 1);
38            state.cachedPages = cached_pages
39        }
40    }
41};
42
43const getters = {
44};
45
46const actions = {
47    savePosition({commit}, data) {
48        commit('SAVE_POSITION', data)
49    },
50    setCachedPage({commit}, pageName) {
51        commit('SET_CACHED_PAGE', pageName)
52    },
53    removeCachedPage({commit}, pageName) {
54        commit('REMOVE_CACHED_PAGE', pageName)
55    }
56};
57
58export default {
59    namespacetrue//这里使用了vuex的命名空间,方便管理
60    state,
61    mutations,
62    getters,
63    actions
64}

/store/index.js

 1import Vue from 'vue'
2import Vuex from 'vuex'
3import CommonModule from './common'
4
5Vue.use(Vuex);
6
7export default new Vuex.Store({
8    strict: process.env.NODE_ENV !== 'production'//严格模式
9    modules: {
10        CommonModule
11    }
12})

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

app.vue

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

 1<template>
2    <div id="app">
3        <div class="app-container">
4            <keep-alive :include="cachedPages">
5                <router-view/>
6            </keep-alive>
7        </div>
8    </div>
9</template>
10
11<script type="text/ecmascript-6">
12    import {mapState} from 'vuex';
13
14    export default {
15        data() {
16            return {}
17        },
18        components: {
19        },
20        created() {
21        },
22        computed: {
23            ...mapState({
24                cachedPagesstate => state.CommonModule.cachedPages
25            })
26        }
27    }
28
</script>
29<style lang="less">
30
</style>

/views/lista.vue

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

/views/lista.vue

 1<template>
2    <div>
3        i am list a
4    </div>
5</template>
6
7<script type="text/ecmascript-6">
8    import scrollPosition from "../mixins/scroll-position";
9
10    export default {
11        name'page-list-a',
12        mixins: [scrollPosition],
13        data() {
14            return {
15            }
16        },
17        beforeRouteLeave(to, from, next) {
18            if (['detail'].some(name => name === to.name)) {
19                from.meta.keepAlive = true;
20                this.$store.dispatch('setCachedPage'from.name).then(() => {
21                    next()
22                });
23            } else {
24                from.meta.keepAlive = false;
25                this.$store.dispatch('removeCachedPage'from.name).then(() => {
26                    next();
27                });
28            }
29        }
30    };
31
</script>
32<style lang="less" scoped>
33
</style>

/views/detail.vue

 1<template>
2    <div>
3        i am detail
4    </div>
5</template>
6
7<script type="text/ecmascript-6">
8    export default {
9        name'page-detail',
10        data() {
11            return {
12            }
13        },
14        beforeRouteLeave(to, from, next) {
15            if (['list-a'].some(name => name === to.name)) {
16                from.meta.keepAlive = true;
17                this.$store.dispatch('setCachedPage'from.name).then(() => {
18                    next()
19                });
20            } else {
21                from.meta.keepAlive = false;
22                this.$store.dispatch('removeCachedPage'from.name).then(() => {
23                    next();
24                });
25            }
26        }
27    };
28
</script>
29<style lang="less" scoped>
30
</style>

list-b.vue

 1<template>
2    <div>
3       i am list b
4    </div>
5</template>
6
7<script type="text/ecmascript-6">
8
9    export default {
10        name'page-list-b',
11        data() {
12        }
13
</script>
14<style lang="less" scoped>
15
</style>

/router/index.js

 1import Vue from 'vue'
2import VueRouter from 'vue-router'
3
4// 重写新版VueRouter的push和replace方法的异常捕捉
5const originalPush = VueRouter.prototype.push;
6VueRouter.prototype.push = function push(location, onResolve, onReject{
7    if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject);
8    return originalPush.call(this, location).catch(err => err)
9};
10Vue.use(VueRouter);
11
12const routes = [
13    {
14        path'/list-a',
15        name'list-a',
16        meta: {
17            title'列表页A'
18        },
19        component() => import ('../views/list-a')
20    },
21    {
22        path'/detail',
23        name'detail',
24        meta: {
25            title'详情页',
26            keepAlivetrue
27        },
28        component() => import ('../views/detail')
29    },
30    {
31        path'/list-b',
32        name'list-b',
33        meta: {
34            title'列表页B'
35        },
36        component() => import ('../views/list-b')
37    }
38];
39
40const router = new VueRouter({
41    mode'history',
42    routes
43});
44
45export default router