keep-alive使用的坑

1,156 阅读5分钟

keep-alive是什么

官方解析参考
  • 目的:解决组件切换的时候,想要保持某些组件的状态,避免反复重复渲染导致的性能问题。(即主要用于保留组件状态或避免重新渲染)
keep-alive的使用场景

参考

通过keep-alive包裹的组件,在不活动时会缓存组件实例,不会对组件进行销毁,再次处于活动状态时,会读取缓存的内容并保存组件状态,不用重复请求接口获取数据。

keep-alive怎么用

官方使用参考
<!-- 失活的组件将会被缓存!-->
<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

注意这个 <keep-alive> 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册。

【重】vue-element-admin中对keep-alive的使用(我的工作中的应用场景)

vue-element-admin是使用vuex来统一管理所有组件,把所有组件的名称存储在一个名叫cacheViews的状态中。代码如下:

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

//cachedViews是一个缓存组件的数组,存储的组价的name
computed: {
    cachedViews() {
      console.log(
        '添加到缓存的页面有:',
        this.$store.state.tagsView.cachedViews
      )
      return this.$store.state.tagsView.cachedViews
    },
}

**注意的是,自己写的路由页面必须带和状态存储的一致的名称,否则keep-alive不起作用。**即需要保存的组件必须含有name,该name必须和cachedViews保存一致。代码如下:

export default {
  name: 'organ-category-manage',//存储在cachedViews的名称一致的时候,keep-alive才起作用
  activated() {
    console.log('触发了organ-category-manage再次获取数据', this.dataForm)
  },
}
keep-alive的更多API

参考官网API

  • props:

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

    <keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。

    当组件在 <keep-alive> 内被切换,它的 activateddeactivated 这两个生命周期钩子函数将会被对应执行。

1.一般用法:

<!-- 基本 -->
<keep-alive>
  <component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
  <comp-a v-if="a > 1"></comp-a>
  <comp-b v-else></comp-b>
</keep-alive>

<!-- 和 `<transition>` 一起使用 -->
<transition>
  <keep-alive>
    <component :is="view"></component>
  </keep-alive>
</transition>

注意,<keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作。如果有上述的多个条件性的子元素,<keep-alive> 要求同时只有一个子元素被渲染。

2.includeexclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

<!-- 逗号分隔字符串 -->
<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>

匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。

3.最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。

<keep-alive :max="10">
  <component :is="view"></component>
</keep-alive>

keep-alive两个重要的生命周期

  • activated:页面第一次进入的时候,钩子触发的顺序是created->mounted->activated

  • deactivated :页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发

keep-alive的坑

  • 需要使用actived来直接取代created或者mounted的生命周期时候做的操作,例如发送请求等。(但是这样强刷,例如按f5的时候不会触发activated的问题)

  • 若actived和created生命周期共存会存在的问题是,第一次访问的时候会触发created和actived这两个生命周期,导致的结果就是请求会发送两次,增加服务器的压力。

  • 若使用actived,把created生命周期删除。会出现的问题:强制刷新,请求没有,原因是强制刷新的时候不会走actived这个生命周期,所以导致请求没有发送请求。

  • 解决强刷不经过actived生命周期的解决办法是添加一个变量来控制该组件切换是否为第一次进入还是非第二次进入。

    data() {
        return {
        	firstEnter:false
        }
    }
    created() {
        this.getDataList()
      },
      activated() {
        if (this.firstEnter) {//非第一次进入的时候
          this.getDataList()
        } else {//第一次进入,标识该组件已经被缓存
          this.firstEnter = true
        }
      },
    
  • 还会存在的问题,例如该缓存的页面中有一个输入框,该输入框是用于搜索数据然后渲染到页面的,所以当在该输入框输入了内容的时候,第二次切换回来的时候就会进入actived生命周期执行getDataList方法获取数据列表,并携带了输入框的条件。解决办法是:用两个状态保存,一个状态保存用户在输入框输入的内容,一个保存用户点击了查询按钮后当前显示在页面上的数据的状态。这样既可以保存用户输入的内容,也可以实现保存组件状态的问题。

     data() {
        return {
        dataForm: {//用于保存用户输入的状态
            pageNo: 1,
            pageSize: 10,
            keyWord: '',
         },
          keepStateDataForm: {//用于作为查询条件的状态
            pageNo: 1,
            pageSize: 10,
            keyWord: '',
          },
        }
    }
    //获取渲染数据的统一方法,包括查询
    getDataList(index) {
          if (index) {
            this.dataForm.pageNo = index
            //查询的时候保存最新的数据列表
            this.keepStateDataForm = JSON.parse(JSON.stringify(this.dataForm))
          }
          this.dataListLoading = true
          let params = {
            pageNo: this.dataForm.pageNo,//使用原数据的pageNo和pageSize
            pageSize: this.dataForm.pageSize,
            keyWord: this.keepStateDataForm.keyWord,
          }
          //发送请求获取数据
          activityInfoList(params).then(res=>{...})
     }
     
     
     /**分页的方法,谨慎使用带参数的getDataList方法,因为会进行一个状态的保存,
     注意的状态保存会影响下一次数据的查询。可以使用下面代码解决,需要传递pageNo的问题
     `this.dataForm.pageNo=1;this.getDataList()`
     
     **/
     // 每页数
     sizeChangeHandle(val) {
         this.dataForm.pageSize = val
         this.dataForm.pageNo = 1
         this.getDataList()
     },
    
    // 当前页
    currentChangeHandle(val) {
        this.dataForm.pageNo = val
        this.getDataList()
    },
    
    reset() {
        this.dataForm = {
            pageNo: 1,
            pageSize: 10,
            keyWord: '',
        }
        this.keepStateDataForm = JSON.parse(JSON.stringify(this.dataForm))
        this.getDataList(1)
    },