首页 > 技术文章 > keep-alive 实现后退不刷新并保持滚动位置

xiaonian8 2020-09-22 19:28 原文

vue可以通过<keep-alive>元素包裹组件,实现缓存,下次使用时不需要重新创建该组件。但存在一个问题:keep-alive包裹的组件中有滚动元素时,keep-alive不会储存滚动位置。实现后退不刷新主要依据keep-alive组件的activated和deactivated这两个生命周期钩子函数。

vue钩子函数的执行顺序:
不使用keep-alive

beforeRouteEnter --> created --> mounted --> destroyed

vi设计http://www.maiqicn.com 办公资源网站大全https://www.wode007.com

使用keep-alive
初次进入页面,beforeRouteEnter --> created --> mounted --> activated --> deactivated
再次进入缓存的页面,只会触发beforeRouteEnter -->activated --> deactivated。created和mounted不会再执行。
其中,activated在keep-alive组件激活时调用.deactivated在keep-alive组件被停用时调用.

Demo实现了后退不刷新,并且返回时滚动到上次浏览的深度。

该demo中,包含三个链接导航。

home --> pageA --> pageB --> pageC

依次前进,每次前进到一个新页面都需要获取数据,而按下后退键后,
从pageC返回到pageB,pageB不再获取新数据,而是使用之前缓存的数据。
从pageB返回到pageA时,pageA不再获取新数据,而是使用之前的数据。并且当pageA存在滚动条时,返回时会滚动到上次浏览高度。
所以,pageA和pageB需要缓存,pageC不需要缓存。

//router.js
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);

const router = new Router({
  mode: 'hash',
  routes: [
    {
      path: '/',
      name: 'home',
      component: () =>
        import('./views/Home.vue'),
      meta: {
        title: '首页',
        keepAlive: false //此组件不需要被缓存
      }
    },
    {
      path: '/pageA',
      name: 'pageA',
      component: () =>
          import('./views/pageA.vue'),
      meta: {
        title: 'pageA',
        keepAlive: true,
        isBack: false
      }
    },
    {
      path: '/pageB',
      name: 'pageB',
      component: () =>
          import('./views/pageB.vue'),
      meta: {
        title: 'pageB',
        keepAlive: true,
        isBack: false
      }
    },
    {
      path: '/pageC',
      name: 'pageC',
      component: () =>
          import('./views/pageC.vue'),
      meta: {
        title: 'pageC',
        keepAlive: false
      }
    }
  ]
});
export default router;
//pageA.vue
<template>
    <div class="page-a">
        <h1>pageA</h1>
        <div>
            <div class="item" v-for="item in items" @click="goPageB">
                {{ item }}
            </div>
        </div>
        <h1 @click="goPageB">go pageB</h1>
    </div>
</template>


<script>
    export default {
        name: 'PageA',
        data() {
            return {
                msg: "我是PageA页面",
                items: Array.from({length:50}, (v,k) => k),
                data: "",
                scrollTop: 0
            };
        },
        beforeRouteEnter(to, from, next) {
            if(from.name == 'pageB'){
                to.meta.isBack = true;
            }

            next();
        },
        mounted() {
            console.log('mounted....');
            // this指向组件的实例,$el指向当前组件的DOM元素
            const $el = this.$el;
            //滚动事件
            $el.addEventListener("scroll", () => {
                //记录位置
                this.scrollTop = $el.scrollTop;
            });
        },
        activated() {
            if(!this.$route.meta.isBack){
                // 如果isBack是false,表明需要获取新数据,否则就不再请求,直接使用缓存的数据
                this.getData();
            } else {
                //恢复滚动条高度
                if(this.scrollTop) {
                    setTimeout(() => {
                        this.$el.scrollTop = this.scrollTop;
                    }, 100);
                }
            }
            // 恢复成默认的false,避免isBack一直是true
            this.$route.meta.isBack = false;

        },
        methods: {
            getData() {
                // getData方法,模拟从后台请求数据
                this.data = "数据";
                console.log('get data')
            },
            goPageB(){
                this.$router.push({ path: "/pageB" });
            },
            back() {
                this.$router.push({ path: "/" });
            }
        },
    }
</script>
<style>
    .page-a {
        height: 100vh;
        overflow-y: auto;
    }
    .item {
        margin: 5px;
        padding: 10px;
        background: #ccc;
    }
</style>

推荐阅读