vue中动态添加和删除组件缓存 keep-alive

keep-alive介绍

  • Vue的抽象组件,自身不会渲染一个DOM元素,也不会出现在父组件链中,能将组件在切换过程中将状态保存在内存中,防止重复渲染DOM
  • 包裹动态组建时,会缓存不活动的组件实例,而不是销毁它们
  • 当组件在 keep-alive内被切换时,它的activated和deactivated这两个生命周期钩子函数将会被执行

Props

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例

匹配规则

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配,这里匿名组件暂时还没有具体搞懂,希望有大神路过此地时指点一二。

注意
  • include 和 exclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示
  • exclude优先级大于include
  • max:最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉
  • keep-alive 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册

字符串和数组表示的区别:

  • 当:include=""为空字符串时,代表被keep-alive包裹的组件都缓存
  • 当:include=[]为空数组时,代表不缓存被keep-alive包裹的组件

<keep-alive>嵌套<keep-alive>对max的影响

  • 外层<keep-alive>中设置的max只对它包裹的组件有影响
  • 外层嵌套的内层<keep-alive>内的缓存组件数量不会加到外层<keep-alive>的max上,
  • 动态删除外层缓存的某组件,则其内层<keep-alive>缓存的组件也会一并删除

keep-alive包裹的组件多出的钩子函数

  • activated:组件激活时调用,在组件第一次渲染时也会被调用,之后每次keep-alive激活时被调用
  • deactivated:组件停用时调用
说明
  • 当组件第一次渲染时activated也会被调用,即beforeCreate -> created -> beforeMount -> mounted -> activated
  • 当跳转另一组件时即当前组件被停用时,则只会调用deactivated,它的beforeDestroy和destroy不会被调用
  • 当再次激活此组件时,则也只会调用activated,其它钩子函数也不会调用
注意
  • 只有组件被keep-alive包裹时,这两个生命周期函数才会被调用,正常组件不会调用这两个函数
  • 使用exclude排除之后的组件,就算包裹在keep-alive中,这两个函数也不会被调用
  • 在服务器端渲染期间不被调用

注意
下面的 思路、实例演示、代码示例 都是基于 动态组件 来实现的,如果Tab切换想通过嵌套路由实现,可参考这篇文章-vue实现Tab切换功能的几种方式

什么是动态组件

让多个组件使用同一个挂载点,并动态切换
component组件和keep-alive一样都是Vue的内置组件,在component里通过v-bind:is来决定哪个组件被渲染,从而实现动态组件切换效果,如

<component :is="componentId"></component>

在做Tab切换时,可通过动态组件来实现,如果用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

动态组件传值

  <component :is="currentTabComponent" :componentTabName='componentTabName'/>

componentTabName:为需要传递的数据,名称可自定义

在各组件中通过prop获取即可
  props:{
      componentTabName:String
  }

需求

在项目中经常会有 页面A -> 页面B -> 页面C 则从 页面C 返回 页面B 时 页面B 使用缓存数据,而页面A 跳到 页面B 时,则页面B每次都请求最新数据。
比如我们在某APP内点击 最新新闻(页面A) 选项 跳转到 新闻列表(页面B) 选择某一条新闻 跳转到 新闻详情(页面C) 页面,我们希望,从新闻详情返回到新闻列表时,直接用刚才请求的数据,而不每次都重新发送请求,而从 最新新闻 跳转到 新闻列表时,则都请求最新的数据

思路

  1. 通过使用keep-alive的include属性有条件的缓存组件
  2. 通过store响应式的修改include属性对应的值
  3. 通过组件内导航钩子beforeRouteEnter、beforeRouteLeave给store提交mutations修改

实例演示

在这里插入图片描述

代码示例

1:在App.vue组件通过computed计算属性响应式的获取store里的keepAliveArr计算属性,并赋值给keep-alive的include属性,并设置最多可缓存5个组件
  <template>
      <div id="app">
          <keep-alive :include="keepAliveArr" :max="5">
              <router-view></router-view>
          </keep-alive>
      </div>
  </template>
  <script>
      export default {
          computed: {
              keepAliveArr() {
                  return this.$store.getters.keepAliveArr
              }
          }
      }
2:在store的mutations中提供状态更改的方法,并通过store的计算属性供外部访问
  import Vue from 'vue'
  import Vuex from 'vuex'
  Vue.use(Vuex);

  export default new Vuex.Store({
      state: {
          //缓存组件数组
          keepAliveArr: []
      },
      mutations: {
          UPDATE_KEEP_ALIVE(state, payload) {
              //当payload.type不为空则代表清除指定缓存组件,否则添加指定组件
              if (payload.type) {
                  let index = state.keepAliveArr.indexOf(payload.keepAlive);
                  if (index !== -1) {
                      state.keepAliveArr.splice(index, 1); //删除数组的缓存的组件
                  }
              } else {
                  let index = state.keepAliveArr.indexOf(payload.keepAlive);
                  if (index === -1) {
                      state.keepAliveArr.push(payload.keepAlive); //添加需要缓存的组件
                  }
              }
          }
      },
      getters: {
          keepAliveArr: state => state.keepAliveArr
      }
  })

3:在组件内通过导航钩子beforeRouteEnter、beforeRouteLeave给store提交mutationsmutations修改缓存组件keepAliveArr的值,

这里page1为goods的上一个页面,page2为下一个页面,通过beforeRouteEnter钩子,不管从哪个页面进入提交commit,缓存当前页面,当离开时判断,如果是
返回上一个页面则删除当前页面缓存,否则不处理。

  beforeRouteEnter(to, from, next) {
       next(vm => { //添加组件缓存
           vm.$store.commit("UPDATE_KEEP_ALIVE", {
               keepAlive: 'goods'
           })
       })
   },

   beforeRouteLeave(to, from, next) {
       if (to.path === '/page1') {//删除缓存
           this.$store.commit("UPDATE_KEEP_ALIVE", {
               type: 1,
               keepAlive: 'goods'
           })
       }
       next()
   },
总结

通过上面三步就可以实现我们的需求,但项目中我们也经常会用到Tab切换,当Tab切换时,我们也希望缓存加载过的组件,就好比我们的需求,如果在新闻列表内加了Tab切换,
如新闻又分 科技、娱乐、财经等Tab,这个时候就是keep-alive嵌套keep-alive了,需要注意的在上面我也都说了,大家注意下就行,只是Tab切换这里我使用的动态组件。

完整代码

goods代码
  <template>
      <div id="dynamic-component-demo">
          <div class="btn" @click="btnJumpClick">跳转到page2</div>
          <nav class="tab-root">
              <span v-for="(tab,index) in tabs"
                    :key="tab"
                    :class="['tab-button', { active: currentTab == index }]"
                    @click='currentTab = index'>
                  {{ tab }}
              </span>
          </nav>
          <!--缓存各个组件-->
          <keep-alive :include="cached" :max="3">
              <component :is="currentTabComponent"/>
          </keep-alive>
      </div>
  </template>

  <script>
      import tabGoodsRecommend from './tab-goods-recommend';
      import tabGoodsDetail from './tab-goods-detail';
      import tabGoodsComment from './tab-goods-comment';

      export default {
          name: "goods",
          components: {tabGoodsRecommend, tabGoodsDetail, tabGoodsComment},
          data() {
              return {
                  cached: 'tab-goods-recommend,tab-goods-detail,tab-goods-comment',
                  currentTab: 0,
                  tabs: ['推荐', '详情', '评价']
              }
          },
          computed: {
              currentTabComponent() {
                  switch (this.currentTab) {
                      case 0:
                          return 'tab-goods-recommend';
                      case 1:
                          return 'tab-goods-detail';
                      case 2:
                          return 'tab-goods-comment';
                  }
              }
          },
          activated() {
              console.log("--demo--activated--");
          },
          deactivated() {
              console.log("--demo--deactivated--");
          },
          beforeRouteEnter(to, from, next) {
              next(vm => { //添加组件缓存
                  vm.$store.commit("UPDATE_KEEP_ALIVE", {
                      keepAlive: 'goods'
                  })
              })
          },

          beforeRouteLeave(to, from, next) {
              if (to.path === '/page1') {//删除缓存
                  this.$store.commit("UPDATE_KEEP_ALIVE", {
                      type: 1,
                      keepAlive: 'goods'
                  })
              }
              next()
          },

          methods: {
              btnJumpClick() {
                  this.$router.push({
                      path: '/page2'
                  })
              },
          }
      }
  </script>

  <style scoped lang="scss">
      .btn {
          width: 100%;
          height: 40px;
          background: #f00;
          font-size: 20px;
          color: white;
      }
      .tab-root {
          display: flex;
          border-bottom: 1px solid #eee;
      }
      .tab-button {
          background: #fff;
          line-height: 40px;
          height: 40px;
          text-align: center;
          flex: 1;
          font-size: 15px;
          font-weight: normal;
      }
      .tab-button.active {
          font-size: 17px;
          font-weight: 500;
          border-bottom: 2px solid #f00;
      }
  </style>
tab-goods-recommend代码
  <template>
      <div class="recommends-tab">
          <ul class="recommends-sidebar">
              <li v-for="recommend in recommends"
                  :key="recommend.id"
                  :class="{ selected: recommend === selectedRecommend }"
                  @click="selectedRecommend = recommend">
                  {{ recommend.title }}
              </li>
          </ul>
          <div class="selected-recommend-container">
              <div class="selected-recommend">
                  <div v-html="selectedRecommend.content"></div>
              </div>
          </div>
      </div>
  </template>

  <script>
      export default {
          name: "tab-goods-recommend",
          data() {
              return {
                  recommends: [{
                          id: 1,
                          title: '母婴',
                          content: '<p>儿童玩具、尿裤湿巾、奶粉辅食</p>'
                      }, {
                          id: 2,
                          title: '鞋包',
                          content: '<p>功能箱包、人气热卖、服饰配件</p>'
                      }, {
                          id: 3,
                          title: '水果',
                          content: '<p>瓜果桃李、海鲜水产、熟食凉菜</p>'
                      }
                  ],
                  selectedRecommend: {}
              }
          },
          created() {
            this.selectedRecommend = this.recommends[0];
        },
      }
  </script>

  <style scoped lang="scss">
      .recommends-tab {
          display: flex;
      }
      .recommends-sidebar {
          width: 20%;
          text-align: center;
          background: #eee;
          height: 100vh;
      }
      .recommends-sidebar li {
          height: 30px;
          line-height: 30px;
      }
      .recommends-sidebar li.selected {
          background: #fff;
          color: red;
      }
      .selected-recommend-container {
          padding-left: 10px;
      }
</style>
tab-goods-detail代码
  <template>
      <div>商品评论</div>
  </template>
  <script>
      export default {
          name: "tab-goods-detail",
      }
  </script>
tab-goods-comment代码
  <template>
      <div>商品详情</div>
  </template>
  <script>
      export default {
          name: "tab-goods-comment",
      }
  </script>

上面的代码应该已经够用,如果需要全部详细代码的就留言吧,我再单独发你

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值