美文网首页前端开发那些事儿
Vue keep-alive 多级路由缓存方案

Vue keep-alive 多级路由缓存方案

作者: silence_1ea5 | 来源:发表于2020-11-15 14:29 被阅读0次

    基于vue-element-admin框架 的多级路由缓存

    https://juejin.im/post/6895228036342513672

    效果图

    image

    1. keep-alive 路由缓存原理

    keep-alive根据路由名称缓存 对应页组件 name 属性必须和 include 数组中一样

    cachedViews 数组由store维护

    <transition name="fade-transform" mode="out-in">
    
      <keep-alive :include="cachedViews">
    
        <router-view />
    
      </keep-alive>
    
    </transition>
    

    2. 路由缓存方案

    • 在vue-element-admin中跟路由是src/layout/component/AppMain文件,只有一级路由,多级路由是新建一个空的路由文件来实现的。

    • 本方案使用一个同一个文件实现多级路由,不用再另外写空路由文件。

    原理

    • keep-alive必须是通过组件名字来匹配的,想用一个组件文件来复用的问题在于如何动态改变组件的名字。

    • 组件是在router中配置的,在这里给组件改名还是比较麻烦的,尝试了各种方法终于实现了动态改变组件名字的效果 (没错就是用的重命名大法~~)。

    • 子路由文件demo:

      import EmptyRoute from '@/layout/EmptyRoute'
      export default {
        path: '/lab',
        component: load('layout/index'),
        redirect: 'noRedirect',
        name: 'layout',
        alwaysShow: true,
        meta: {
          title: '实验室',
        },
        children: [
          {
            path: 'todo-list',
            component: load('views/lab/todo/list/index'),
            name: 'lab-todo-list',
            meta: {
              title: '待办事项'
            }
          },
          {
            path: 'todo-list-detail/:id',
            component: load('views/lab/todo/list/detail'),
            name: 'lab-todo-list-detail',
            hidden: true,
            meta: {
              title: '查看待办事项',
              activeMenu: '/lab/todo-list',
            }
          },
          {
            path: 'common',
            name: 'common',
            redirect: 'noRedirect',//这个别忘了加
            component: { ...EmptyRoute, name: 'common' },//子路由
            alwaysShow: true,
            meta: {
              title: '通用要求'
            },
            children: [
              {
                path: 'fairness',
                component: load('views/lab/common/fairness/index'),
                name: 'lab-common-fairness',
                meta: {
                  title: '公正性',
                }
              },
              {
                path: 'privacy',
                name: 'privacy',
                redirect: 'noRedirect',
                component: { ...EmptyRoute, name: 'privacy' },//子路由
                alwaysShow: true,
                meta: {
                  title: '保密性'
                },
                children: [
                  {
                    path: 'agreement',
                    component: load('views/lab/common/privacy/agreement/index'),
                    name: 'lab-common-privacy-agreement',
                    meta: {
                      title: '保密协议',
                    }
                  }
                ]
              }
            ]
          }
        ]
      }
      
    • 路由文件EmptyRoute.vue

      <template>
        <transition name="fade-transform" mode="out-in">
          <keep-alive :include="cachedViews">
            <router-view />
          </keep-alive>
        </transition>
      </template>
      <script>
      
      // 配置是否开启路由缓存
      
      import { needTagsView } from "@/settings";
      export default {
        computed: {
          //从store中获取本级路由需要缓存的路由组件名字
          cachedViews() {
            if (this.routeName && needTagsView) {
              const cached = this.$store.getters.cached;
              const cachedViews = cached ? cached[this.routeName] : [];
              return cachedViews || [];
            }
            return [];
          }
        },
        data() {
          return {
          //跟路由名字 这个是路由默认的名字 代替layout中的MainApp
            routeName: "layout"
          };
        },
        created() {
          //这里重命名路由
          this.routeName = this.$options.name || "layout";
        }
      };
      </script>
      
    • store中的 tagsView.js 改造

      • 一切基于visitedViews: 根据数组中各级route的matched 数组来设置各级别路由应该缓存的路由名字,由cached对象保存,核心方法:setMatched,matched对象使用路由的名字作为key值
      image
    • 代码
       /* eslint-disable no-shadow */
    const state = {
      isRefresh: false,//是否是刷新的
      cached: {},
      visitedViews: [],
    }
    const mutations = {}
    function filterView(view) {
      if (!view) return view
      const {
        fullPath,
        name,
        path,
        meta,
        params,
        query,
        matched
      } = view
      return {
        fullPath,
        name,
        path,
        meta,
        params,
        query,
        matched: matched ? matched.map(i => ({
          meta: i.meta,
          name: i.name,
          path: i.path,
        })) : undefined
      }
    }
    const actions = {
      retsetState({ state }) {
        state.visitedViews = []
        state.cached = {}
      },
      setMatched({ dispatch, state }) {
        const obj = {}
        state.visitedViews.forEach(view => {
          if (view.meta.affix && view.meta.matchedKey) {
            let arr = obj[view.meta.matchedKey] || []
            if (!arr.includes(view.name)) {
              arr.push(view.name)
            }
            obj[view.meta.matchedKey] = arr
          } else if (view.matched && !view.meta.noCache) {
            const matched = view.matched
            const len = matched.length
            if (len < 2) return
            for (let idx = 0; idx < matched.length; idx++) {
              const key = matched[idx].name;
              if (idx < len - 1) {
                const vnext = matched[idx + 1];
                const { meta, name } = vnext
                if (meta && (meta.affix || !meta.noCache)) {
                  let arr = obj[key] || []
                  if (!arr.includes(name)) {
                    arr.push(name)
                  }
                  obj[key] = arr
                }
              }
            }
          }
        })
        state.cached = obj
      },
      addView({ dispatch, state }, view) {
        try {
          if (state.visitedViews.some(v => v.path === view.path) && state.isRefresh===false) return
          state.isRefresh = false
          view = filterView(view)
          const idx = state.visitedViews.findIndex(v => v.name === view.name)
          if (idx > -1) {
            state.visitedViews.splice(idx, 1, { ...view, title: view.meta.title || '' })
          } else {
            state.visitedViews.push(
              { ...view, title: view.meta.title || '' }
            )
          }
          dispatch('setMatched')
        } catch (error) {
          console.log('addView', error);
        }
      },
      delView({ dispatch, state }, view) {
        return new Promise(resolve => {
          const idx = state.visitedViews.findIndex(i => i.path === view.path)
          if (idx > -1) {
            state.visitedViews.splice(idx, 1)
          }
          dispatch('setMatched')
          resolve({ visitedViews: state.visitedViews })
        })
      },
      refreshView({ dispatch, state }, view) {
        return new Promise(resolve => {
          let name = view.name
          let key = 'layout'
          if (view.matched) {
            const len = view.matched.length
            key = view.matched[len - 2].name
          }
          state.cached[key] = state.cached[key].filter(i => i !== name)
          state.isRefresh = true
          resolve()
        })
      },
      delOthersViews({ dispatch, state }, view) {
        return new Promise(resolve => {
          let arr = state.visitedViews.filter(i => i.meta.affix)
          if (view && !view.meta.affix) {
            arr.push(view)
          }
          state.visitedViews = arr
          dispatch('setMatched')
          resolve({ visitedViews: arr })
        })
      },
    }
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }
    
    • layout中TagsView组件方法改造:调用actions方法变化
    initTags() {
      this.affixTags = this.filterAffixTags(this.routes)
      for (const tag of this.affixTags) {
        // Must have tag name
        if (tag.name) {
          this.$store.dispatch('tagsView/addView', tag)
        }
      }
    }
    addTags() {
      const route = this.getActiveRoute(this.$route)
      const { name } = route
      if (name) {
        this.$store.dispatch('tagsView/addView', route)
      }
      return false
    },
    refreshSelectedTag(view) {
      this.$store.dispatch('tagsView/refreshView', view).then(() => {
        const { fullPath } = view
        this.$nextTick(() => {
          this.$router.replace({
            path: '/redirect' + fullPath
          })
        })
      })
    },
    closeSelectedTag(view) {
      if (view.meta && view.meta.affix) return
      this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
        if (this.isActive(view)) {
          this.toLastView(visitedViews, view)
        }
      })
    },
    closeOthersTags() {
      this.$router.push(this.selectedTag)
      this.$store.dispatch('tagsView/delOthersViews', this.selectedTag).then(() => {
        this.moveToCurrentTag()
      })
    },
    closeAllTags(view) {
      this.$store.dispatch('tagsView/delOthersViews').then(({ visitedViews }) => {
        this.toLastView(visitedViews, view)
      })
    },
    
    • layout/index 中的改造

      把 AppMain 标签更换
      <section class="app-main">
        <EmptyRoute></EmptyRoute>
      </section>
      样式当然照搬过来
      

    经过以上改造,就可以愉快的写多级路由了!!!

    相关文章

      网友评论

        本文标题:Vue keep-alive 多级路由缓存方案

        本文链接:https://www.haomeiwen.com/subject/lxunbktx.html