vue缓存功能实现

lishihuan大约 3 分钟

vue缓存功能实现

参考:https://www.jianshu.com/p/62cbcac077aaopen in new window

主要针对的是在移动端,需要支持缓存、手动刷新、自动刷新

  • 添加界面保存后,需要手动刷新列表页面
  • 返回一级页面,需要清除缓存,防止第二次在进入数据一直不重新加载
  • 需求1: pageA -> pageB -> pageC 缓存pageB: 从pageA进入pageB,刷新页面并缓存页面; 从pageC返回pageB,不刷新页面

  • 需求2: 使用缓存页面,大多数都是列表页进入详情页,所以还需要考虑列表页的滚动位置的问题. 即:pageC返回pageB时,pageB要保持在离开时的位置

需求1

pageA -> pageB -> pageC

场景:首页需要清除全部缓存,列表页面进入详情需要缓存详情界面

pageA: 首页(清除缓存的页面,不然缓存的页面一直不能刷新,显然不对)

pageB:列表页面(默认是需要进行缓存的,从详情界面返回来不应该重新刷新页面)

pageC: 详情界面

1. 在 vuex(或其他存储方案) 声明两个数组

{
    state: () => ({
        keepAliveViews: [],
        notAliveViews: [],
    }),

    mutations: {
        /**
         * 记录需要缓存的路由视图
         */
        saveKeepAliveViews(state, { keepAliveViews }) {
          state.keepAliveViews = keepAliveViews;
        },

        /**
         * 清除页面缓存
         */
        clearCacheView(state, { notAliveViews }) {
          state.notAliveViews = notAliveViews;
        },
    },
}

2. 在定义路由时,在meta中添加属性keepAlive:true

{
    name: "pageB",
    path: "page-b",
    component: () => import("***/pageB.vue"),
    meta: {
        keepAlive: true,
    },
},

3. 在适当位置遍历路由表,记录需要缓存的路由的组件名称

可以写在app.vue组件下

注意: 需要注意的是 组件name路由name保持一致 否则可能缓存不生效

// 记录需缓存的路由/组件
const keepAliveViews = [];
router.getRoutes().forEach((routeItem) => {
    if (routeItem?.meta?.keepAlive) {
        // 组件name和路由name保持一致, 所以可以直接使用routeItem.name
        // 也可以在 meta 中添加属性 compName 来用,或其他方案
        keepAliveViews.push(routeItem.name);
    }
});
store.commit("saveKeepAliveViews", { keepAliveViews });

4. 在路由根组件中

keep-alive会缓存include中存在的组件,会清除exclude中的组件缓存;

//  template
<router-view v-slot="{ Component }">
    <keep-alive :include="keepAliveViews" :exclude="notAliveViews">
        <component :is="Component" />
    </keep-alive>
</router-view>
computed: {
    getKeepAliveViews(){
        return this.$store.state.keepAliveViews;
    },
    getNotAliveViews(){
            return this.$store.state.notAliveViews
    }
}

5. 清除缓存

清除缓存共2处,

  • 一是在路由设置清除缓存实现自动清除
  • 二是手动清除,添加界面涉及到业务保存需要手动清除缓存

5.1 在路由js中

 // 清除缓存的组件
 export function clearCacheView(destroyCompNames=[]) {
  store.dispatch("setNotAliveViews", destroyCompNames);
  console.log("notAliveViews:" + JSON.stringify(store.state.notAliveViews));
  // 清除缓存后,要重置数组为空,下次才能再次缓存
  // 实际上不知道什么时候会完成缓存的清除,这里取500ms,一般满足需求
  setTimeout(() => {
    var notAliveViews = [];
    store.dispatch("setNotAliveViews", notAliveViews);
  }, 500);
}


返回pageA时,手动清除pageB的缓存;

{
    name: "pageA",
    path: "page-a",
    component: () => import("***/pageA.vue"),
    meta: {},
    beforeEnter: () => {
      clearCacheView(["pageB"]); // 这里的"pageB"是页面pageB的组件名称
    },

5.2 手动调用清除缓存

在main.js 中

import {clearCacheView} from "./router";
myApp.config.globalProperties.$clearCacheView = clearCacheView;

保存方法中调用清除

save(){
	// 业务保存
 	Toast('操作成功')
 	this.$clearCacheView([ 'pageB' ]);
 	this.$router.back();
}

需求2

缓存页面记录滚动条位置

1. 在 vuex(或其他存储方案) 声明一个数组

{
    state() {
        return {
            keepAliveViewsScrollPostion: [],
        };
    },

    mutations: {
        // 设置缓存页面滚动元素的位置
        setkeepAliveViewsScrollPostion(state, { routeName, list }) {
            const item = state.keepAliveViewsScrollPostion.find((t) => t.routeName === routeName);
            if (!item) {
                state.keepAliveViewsScrollPostion.push({ routeName, list });
            } else {
                item.list = list;
            }
        },
    },
}

2. 在定义路由时,在meta中添加属性scrollEls

特别说明需要保证 scrollEls 指定要的元素需要 overflow-y: auto;

{
    name: "pageB",
    path: "page-b",
    component: () => import("***/pageB.vue"),
    meta: {
      keepAlive: true,
      scrollEls: [".scroll-list"], // 数组形式,可添加多个可滚动元素
    },
},

3. 在路由守卫中

/**
 * 全局前置守卫
 */
router.beforeEach((to, from) => {
  // 缓存页面:记录滚动位置
  if (from.meta.scrollEls) {
    const scrollObj: any = { routeName: from.name, list: [] };
    from.meta.scrollEls.forEach((element) => {
      const el = document.querySelector(element);
      if (el) {
        scrollObj.list.push({
          el: element,
          top: el.scrollTop,
        });
      }
    });
    store.commit("setkeepAliveViewsScrollPostion", scrollObj);
  }
});


/**
 * 全局后置钩子
 */
router.afterEach((to, from) => {
  // 缓存页面:滚动到指定位置
  nextTick(() => {
    if (to.meta.scrollEls) {
      const item = store.state.keepAliveViewsScrollPostion.find((t) => t.routeName === to.name);
      if (!item) return;
      item.list.forEach((item2) => {
        const el = document.querySelector(item2.el);
        if (el) {
          el.scrollTop = item2.top;
        }
      });
     
    }
  });
});