keep-alive 多级路由 (若依动态路由) 缓存问题

1,855 阅读3分钟

keep-alive 多级路由缓存问题 因为只支持二级路由缓存 三级路由无法支持 网上也有大佬的总结 就是三级也能缓存实际缓存的是二级 假如二级有两个三级子集 缓存就会出问题。所以说keep-alive只支持二级路由缓存。

1、因为左侧菜单栏强依赖路由数据

因为左侧菜单栏强依赖路由数据 于是 把路由元数据和菜单栏数据解耦 分成两个数据 左侧菜单栏数据(menuRoutes) 和route路由里实际使用的路由数据(routes)

state: {
    routes: [], // 路由
    menuRoutes: [], // 菜单路由
},

2、因为route路由当前是三层无法缓存

因为route路由当前是三层无法缓存 于是 把接口返回的三层路由 打平 打到二层去 并把三级路由的二级目录路由干掉。

// 遍历后台传来的路由字符串,转换为组件对象 =》菜单路由
function filterMenuRouter(asyncRouterMap) {
  return asyncRouterMap.filter((route) => {
    if (route.component) {
      // Layout组件特殊处理
      if (route.component === "Layout") {
        route.component = Layout;
      } else {
        route.component = loadLazyView(route.component);
      }
    }
    if (route.children != null && route.children && route.children.length) {
      route.children = filterMenuRouter(route.children);
    }
    return true;
  });
}
// 遍历后台传来的路由字符串,转换为组件对象=》实际路由
function filterAsyncRouter(asyncRouterMap) {
  return asyncRouterMap.filter((route) => {
    if (route.component) {
      // Layout组件特殊处理
      if (route.component === "Layout") {
        route.component = Layout;
      } else {
        route.component = loadLazyView(route.component);
      }
    }
    if (route.children != null && route.children && route.children.length) {
      if (route.children) {
        let temp = [];
        for (const child in route.children) {
          const temp_child = route.children[child];
          if (temp_child.children) {
            for (let i in temp_child.children) {
              const t2 = temp_child.children[i];
              const temp_child2 = { ...t2 };
              temp_child2.path = temp_child.path + "/" + t2.path;
              temp.push(temp_child2);
            }
            delete route.children[child];
          }
        }
        route.children = route.children.concat(temp);
      }
      route.children = filterAsyncRouter(route.children);
    }
    return true;
  });
}

3、路由数据与菜单栏数据解耦之后

路由数据与菜单栏数据解耦之后 出现一个新问题 因为面包屑强依赖于路由的to.matched 因为三级路由 可以直接动态依赖 matched来做面包屑 但是路由数据打平之后 三层路由会少一层面包屑 此处需要递归处理 从菜单路由数据里 取出 类似于to.matched数据 实现面包屑问题

// 获取第一层路由对象
findFirstRoute(route) {
  let routes = this.$store.state.permission.menuRoutes;
  for (let i in routes) {
    let el = routes[i];
    if (el.name === route.name) {
      return el;
    }
  }
},
// 获取层级路由
getPath(arr, key, pathAll = []) {
  for (let i = 0, len = arr.length; i < len; i++) {
    pathAll.push(arr[i]);
    if (arr[i].name === key) {
      return pathAll;
    } else if (arr[i].children && arr[i].children.length >= 1) {
      if (this.getPath(arr[i].children, key, pathAll) != false)
        return pathAll;
    }
    pathAll.pop();
  }
  return false;
},

4、keep-alive 可以缓存组件实例

keep-alive 可以缓存组件实例 是通过 include="['Aaa','Bbb']" 来匹配组件的name是不是 在includes里(区分大小写) 如果是 就缓存下来 如果不是 就不缓存

<keep-alive :include="cachedViews"></keep-alive>

5、keep-alive内的route-view 上可以绑定key

keep-alive内的route-view 上可以绑定key 如果不绑定key那就是一个被缓存的页面 只会缓存一个vue实例 但是如果绑定key key都是唯一的 一个被缓存的页面可以有很多被缓存的vue实例 如果重复进相同的key缓存页面 就会产生一些奇怪的问题

<keep-alive :include="cachedViews">
    <router-view :key="key" />
</keep-alive>

6、奇怪问题的解决方案:

当route-view 上可以绑定key值 可以在this.$vnode.parent.componentInstance.cache 里 查到当前所有被缓存的vue实例 我们判断一下 如果当前页面已经有一个缓存了 那就把这个缓存实例给删除 留着最新的缓存实例 就避免了 操作了n次 突然有一次 一进去就有缓存的问题。

//创建一个全局混入的js文件 挂载到main.js下
const mixin = {
  // 混入数据
  data() {
    return {
      viewData:{}
    };
  },
  // 混入公用组件
  components: {},
  // 处理一个页面缓存多个vue实例的问题
  beforeRouteLeave(to, from, next) {
    try {
      const cachedViews = this.$store.state.tagsView.cachedViews;
      // const tabFlag = this.$store.state.tabsView.tabFlag;
      // const findInCache = cachedViews.find(v => v.includes(from.name))
      if (cachedViews.includes(to.name)) {
        // 删除缓存
        // this.$store.commit("tabsView/SET_CACHEDFLAG", true);
        // this.$store.dispatch("tabsView/delTabsView", to.name);
        const keepAliveInstance = this.findKeepAlive();
        if (!keepAliveInstance) {
          return next();
        }
        // debugger;
        const { cache } = keepAliveInstance;
        console.log("cache", cache);
        const cacheKeys = Object.keys(cache);
        let componentsName = to.name.replace(
          to.name.charAt(0),
          to.name.charAt(0).toLowerCase()
        );
        let count = 0;
        cacheKeys.length &&
          cacheKeys.forEach((el) => {
            if (el.includes(componentsName)) {
              console.log(count, el, componentsName);
              count++;
            }
          });
        console.log("count", count);
        if (count > 1) {
          const delKey = cacheKeys.find((key) => key.includes(componentsName));
          delKey && (cache[delKey] = null);
          Reflect.deleteProperty(cache, delKey);
        }
        // this.$destroy() // 调用组件的销毁
      }
      next();
    } catch (error) {
      next();
    }
  },
  // 混入过滤器
  filters: {},
  // 生命周期钩子函数混入
  created () {
  },
  // 混入方法
  methods: {
    // 查找缓存
    findKeepAlive() {
      const vnodeParent = this.$vnode.parent;
      if (vnodeParent.tag.includes("keep-alive")) {
        return vnodeParent.componentInstance;
      }
      return null;
    },
  },

};
export default mixin;

注意点:

keep-alive里的 cachedViews中存的路由名必须首字母大写,切与组件的name一致

tips:当前优化

1、只支持三级路由 如果有四级及其以上的路由 需要和产品沟通 在二级或三级路由处做出优化 调整为最多三级路由

2、路由缓存,keep-alive会把缓存的页面给冻结住,切换前后页面不会刷新,如果需要刷新,需要在原来的基础上,用keep-alive的activated、deactivated两个生命周期,进行按需操作。