vue中使用keep-alive组件缓存页面

一、keep-alive 组件介绍

keep-alive 是vue的内置组件,作用是缓存不活动的组件实例,在切换组件时将状态保留在内存(vuex)中,防止重新渲染dom,减少页面加载以及性能消耗,同时提高用户体验性。

该组件有三个 props 属性:

  • include:字符串、正则表达式或数组;只有名称(name)匹配的 vue组件 会被缓存

  • exclude:字符串、正则表达式或数组;任何名称(name)匹配的 vue组件 都不会被缓存

  • max:数字;表示这个 keep-alive 最多可缓存多少个组件实例;缓存的组件实例达到这个最大值之后,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁

    注:
    	1、上述的 name 指的是 vue组件 中的 name,而不是路由router中的name!
    	2、匹配时,首先匹配组件自身的 name 值,若 name 不可用,则匹配 它的局部注册名称(父组件 components 中的键值);若两者皆没有,则为匿名组件,匿名组件不能被匹配
    	3、匹配时,name 区分大小写!
    

Vue.js 官网上的使用案例:

<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

二、项目中的实际使用

1、keep-alive 使用的逻辑步骤
  1. 首先,在添加 路由 的时候,定义一个属性,用于动态判断该组件是否需要缓存;此处项目中利用meta中的 noCache 属性来判断,noCache 的值为 true时,表明该组件不需要缓存;noCache 的值为 false 时,表明该组件需要缓存

    const mciFunctionRoutes = {
      path: '/function',
      component: Layout,
      meta: {
        title: '业务功能'
      },
      children: [
        {
          path: 'projectManage',
          component: PlaceholderView,
          meta: { title: '项目管理', noCache: true },
          children: [
            {
              path: 'myProject',
              component: () => import('@/views/mci/projectManage/myProject'),
              name: 'myProject',
              meta: { title: '我的项目', noCache: false }
            },
            {
              path: 'projectCountView',
              component: () => import('@/views/mci/projectManage/projectCountView'),
              name: 'projectCountView',
              meta: { title: '项目统计视图', noCache: true }
            }
          ]
        }
      ]
    }
    

    在创建router实例的时候,还需加上 scrollBehavior 方法,如下:

    const router = new Router({
      mode: 'history',
      routes: constantRoutes.concat(asyncRoutes),
      scrollBehavior (to, from, savedPosition) {
        if (savedPosition) {
          return savedPosition
        } else {
          return {
            x: 0,
            y: 0
          }
        }
      }
    });
    
  2. 在 vuex 中存储需要缓存的组件实例,可以存储它们的 name(这就要求每个组件的 name 值都是唯一的);此处存储的 name 是router路由中的name,而 keep-alive 中使用的 又是 组件的 name 名称,所以 此处有个潜规则,就是router中定义的name 需要与 组件内部定义的 name 值一致

    state: {
        cachedViews: commonUtil.decodeQuery(sessionStorage.getItem('cachedViews')) || [] // 只存储路由name(不会始终包含常驻标签)
    },
    
    mutations: {
    	// 根据 路由中的meta的 noCache 属性,动态判断该组件是否需要缓存
        ADD_CACHED_VIEW: (state, view) => {
          if (state.cachedViews.includes(view.name)) return;
          if (!view.meta.noCache) {
            let cachedViews = state.cachedViews;
            cachedViews.push(view.name);
            sessionStorage.setItem('cachedViews',  commonUtil.encodeQuery(cachedViews));
            state.cachedViews = cachedViews;
          }
        },
            
        DEL_CACHED_VIEW: (state, view) => {
          for (const i of state.cachedViews) {
            if (i === view.name) {
              const index = state.cachedViews.indexOf(i);
              let cachedViews = state.cachedViews;
              cachedViews.splice(index, 1);
              sessionStorage.setItem('cachedViews',  commonUtil.encodeQuery(cachedViews));
              state.cachedViews = cachedViews;
              break;
            }
          }
          sessionStorage.setItem('cachedViews',commonUtil.encodeQuery(state.cachedViews));
        }
    },
    
    actions: {
        addCachedView({commit}, view) {
          commit('ADD_CACHED_VIEW', view)
        },
    
        delCachedView({commit, state}, view) {
          return new Promise(resolve => {
            commit('DEL_CACHED_VIEW', view);
            resolve([...state.cachedViews])
          })
        }
    }
    
  3. 在需要存储 需要缓存的组件实例 时,通过 dispatch 调用 vuex中的 addCachedView 方法,存储对应路由的 name值

    this.$store.dispatch('addCachedView', this.$route);
    
  4. 在需要缓存的 router-view 组件上包裹 keep-alive 组件,在 keep-alive 中,给 include 赋值需要缓存的组件实例即可;此处设置最多可缓存 10 个vue组件实例

    <keep-alive :include="$store.state.tagsbar.cachedViews" :max="10">
        <router-view />
    </keep-alive>
    
  5. 在组件的 name 已经存在于 vuex中的cachedViews数组中,但该组件不需要再缓存时,只需要移除 cachedViews 中的该元素即可实现重新渲染加载该组件;此处使用 dispatch 调用 delCachedView 方法即可

    this.$store.dispatch('delCachedView', this.$route);
    
2、嵌套的router-view,都要添加keep-alive

因为 keep-alive 组件是用在一个直属的子组件被开关的情形,所以 当项目中存在 组件嵌套关系 时,则 keep-alive 的缓存将不起作用。例如:

// mciFunctionRoutes 路由文件中
const mciFunctionRoutes = {
  path: '/function',
  component: Layout,
  meta: {
    title: '业务功能'
  },
  children: [
    {
      path: 'projectManage',
      component: PlaceholderView,
      meta: { title: '项目管理', noCache: true },
      children: [
        {
          path: 'myProject',
          component: () => import('@/views/mci/projectManage/myProject'),
          name: 'myProject',
          meta: { title: '我的项目', noCache: false }
        },
        {
          path: 'projectCountView',
          component: () => import('@/views/mci/projectManage/projectCountView'),
          name: 'projectCountView',
          meta: { title: '项目统计视图', noCache: true }
        }
      ]
    }
  ]
}

// index.js 路由文件中
export const constantRoutes = [
  {
    path: '/',
    component: Layout,
    redirect: 'dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index'),
        name: 'dashboard',
        meta: {title: '首页', noCache: true, affix: true}
      }
    ]
  }, {
    ...mciFunctionRoutes
  }
]

在 index.js 文件中,dashboard 页面(首页)相当于直接注册在 Layout 页面中的 router-view 组件上,而 myProject 页面(我的项目)相当于注册在 PlaceholderView 页面中 的 router-view 组件上;而 PlaceholderView 组件则相当于注册在 Layout 页面中的 router-view 组件上,类似 父子关系。

在这种情况下,若只在 layout.vue 文件中添加 keep-alive 组件,则会导致 myProject 页面(我的项目)缓存不成功,还需在 PlaceholderView.vue 文件中同时添加 keep-alive 组件才可,如下:

// layout.vue 文件中
<keep-alive :include="$store.state.tagsbar.cachedViews" :max="10">
    <router-view />
</keep-alive>

// placeholerView.vue 文件中
<keep-alive :include="$store.state.tagsbar.cachedViews" :max="10">
    <router-view />
</keep-alive>
3、beforeRouteEnter 和 beforeRouteLeave

一般可用其处理 某些页面的动态缓存,比如,当从a页面跳转到b页面时,则设置 b页面 不加入缓存,从其他页面跳转到b页面时,则设置 b页面 加入缓存,如下:

// 在即将进入当前页面时调用,在 created 和 mounted 钩子函数之前调用
beforeRouteEnter(to, from, next) {
  // 逻辑处理
  if(from.path == "/function/businessReport/onSaleReport") {
    to.meta.noCache = true;
  } else {
    to.meta.noCache = false;
  }
  
  next();
}

// 在即将离开当前页面时调用
beforeRouteLeave(to, from, next) {
  // 逻辑处理

  next();
}
注:
	beforeRouteEnter 和 beforeRouteLeave 两个钩子必须写在有配置路由的页面上才有效!
4、手动销毁被keep-alive缓存的实例

在关闭某标签页时,已经将该组件的name从 缓存数组 中移除,但该缓存实例仍未被销毁,即可使用以下 混入对象,以下代码中的watch即可监听到 visitedViews 的变化,从而手动销毁不在其中的实例。

const destoryCompMixin = {
  data() {
    return {
      routePath: ''
    }
  },
  mounted() {
    this.routePath = this.$route.path
  },
  computed: {
    visitedViews() {
      return this.$store.state.tagsbar.visitedViews
    }
  },
  watch: {
    visitedViews(value) {
      let flag = value.some(item => {
        return this.routePath == item.path;
      })
      if(!flag) {
        this.$destroy(this.$options.name)
      }
    }
  }
}
  
export default destoryCompMixin;
5、activated 和 deactivated

只有使用了 keep-alive 包裹的组件有这两个生命周期,activated 的作用 同 mounted,在页面初始化时加载,但由于使用了 keep-alive缓存 之后,再次回到页面不会执行mounted钩子函数,但会执行 activated 钩子函数,所以 初始化查询 等操作可放在 activated 中。

vue组件在keep-alive中被切换时,这个组件的 activateddeactivated 两个生命周期钩子函数将会被对应执行。

activated 在 第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用;调用顺序大致为:beforeMount=>mounted=> activated 进入缓存组件 => 执行 beforeRouteEnter回调

deactivated 可看作 beforeDestroy 的替代,因为缓存组件不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁);调用顺序大致为:beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)

6、使用 keep-alive 时,路由层级不同的组件之间跳转,缓存会失效

解决方案待学习研究......

posted @ 2021-07-11 16:17  Upward123  阅读(226)  评论(0编辑  收藏  举报