Vue2 - keep-alive 作用和原理

1,介绍和作用

<!-- 非活跃的组件将会被缓存! -->
<keep-alive>
  <component :is="activeComponent" />
</keep-alive>

1,是一个内部组件,用于缓存组件实例。在组件切换时,不用重新创建而是使用缓存中的组件实例。

  • 可以避免创建组件的开销。
  • 可以保留组件状态

2,有3个属性:

  • includeexclude 属性,可以控制哪些组件进入缓存。
  • max 属性可以设置最大缓存数,当缓存的实例超过该数时,vue会移除最久没有使用的组件缓存。
<!-- 以英文逗号分隔的字符串 -->
<keep-alive include="Comp1,Comp2">
  <component :is="view" />
</keep-alive>
<!-- 数组 -->
<keep-alive :include="['Comp1', 'Comp2']">
  <component :is="view" />
</keep-alive>

3,受 keep-alive 影响,内部所有嵌套的组件都有2个生命周期函数。activateddeactivated。第1次 activated 是在 mounted 之后。

4,当嵌套组件是 <router-view> 时,缓存的是路由对应的组件。

因为 <router-view> 是函数式组件,只有一个目的,生成虚拟DOM。它没有状态,不生成实例。有穿透的效果。

<keep-alive>
  <router-view></router-view>
</keep-alive>

2,原理

源码

keep-alive 在内部维护了一个缓存对象和 keys 数组:

// keep-alive 组件的生命周期函数
created () {
   
  this.cache = Object.create(null)
  this.keys = []
}
  • keys 数组记录当前缓存组件的 key,如果组件没有指定,则会自动为组件生成唯一的 key

  • cache 对象以 key 为键,vnode 为值,来缓存组件对应的虚拟 DOM。

keep-alive 组件的 render 函数中,大致逻辑:

判断当前渲染的 vnode 是否有对应的缓存,有则从缓存中读取对应组件实例,没有则将其缓存。当缓存数量 > max 时,会移除掉 keys 数组的第1个元素。

// 更新逻辑其实在 update 函数中,这里结合在一起容易理解。
render(){
   
  const slot = this.$slots.default; // 获取默认插槽
  const vnode = getFirstComponentChild(slot); // 得到插槽中的第一个组件的 vnode
  const name = getComponentName(vnode.componentOptions); // 获取组件名字
  const {
    cache, keys } = this; // 获取当前的缓存对象和key数组
  const key = ...; // 获取组件的key值,若没有,会按照规则自动生成
  if (cache[key]) {
   
    // 有缓存
    // 关键:重用组件实例
    vnode.componentInstance = cache[key].componentInstance
    remove(keys, key); // 删除key
    // 将 key加入到数组末尾,这样是为了保证最近使用的组件在数组中靠后,反之靠前
    keys.push(key); 
  } else {
   
    // 无缓存,则进行缓存
    cache[key] = vnode
    keys.push(key)
    if (this.max && keys.length > parseInt(this.max)) {
   
      // 超过最大缓存数量,移除第一个key对应的缓存
      pruneCacheEntry(cache, keys[0], keys, this._vnode)
    }
  }
  return vnode || (slot && slot[0])
}

注意到,render() 返回的就是插槽中的第一个组件的 vnode。所以页面上显示的也就是这个子组件。

3,使用场景

在后台管理系统中比较常见,比如有2种情况就会用到:

  • 侧边栏的菜单,一般对应的是路由。切换页面(路由)时想保留之前的页面信息。
  • 列表页进详情页时,想保留列表页信息,因为列表可能是通过输入框等多个过滤条件得到的,如果从详情页返回列表页再次操作会比较繁琐。

这2种情况,都可以做成 tab 选项卡。

3.1,效果展示

在这里插入图片描述

3.2,实现思路

首先维护一个【tab选项卡】的状态,比如放到 Vuex 中。不放到组件内部,是因为不好使用,因为可能不止侧边栏会添加 tab,其他地方可能也会添加页面到 tab 选项卡。

export default new Vuex.Store({
   
  modules: {
   
    tabs: {
   
      namespaced: true,
      state: {
   
        pageNames: [] // 选项卡的页面
      },
      mutations: {
   
        addPage(state, newPageName) {
   
          if (!state.pageNames.includes(newPageName)) {
   
            state.pageNames.push(newPageName)
          }
        },
        removePage(state, pageName) {
   
          const index = state.pageNames.indexOf(pageName)
          if (index >= 0) {
   
            state.pageNames.splice(index, 1)
          }
        }
      }
    }
  }
})

其他的看代码就一目了然了:(省略了CSS)

<template>
  <div>
    <!-- 固定在页面左侧 -->
    <div>
      <h1>侧边栏</h1>
      <ul>
        <router-link
          v-for="item in $router.options.routes"
          :key="item.path"
          tag="li"
          :to="{ name: item.name }"
          active-class="blue"
        >
          <span>{
  { item.name }}</span>
          <button @click="handleAddPage(item.name)">+</button>
        </router-link>
      </ul>
    </div>
    
    <!-- 页面内容区域 -->
    <div>
      <!-- 固定在页面顶部,左侧和侧边栏对齐 -->
      <div v-if="$store.state.tabs.pageNames.length">
        <span>tab选项卡:</span>
        <ul>
          <router-link
            v-for="pageName in $store.state.tabs.pageNames"
            :key="pageName"
            tag="li"
            active-class="green"
            :to="{ name: pageName }"
          >
            <span>{
  { pageName }}</span>
            <button @click="handleRemovePage(pageName)">x</button>
          </router-link>
        </ul>
      </div>
      <!-- 页面内容展示区域 -->
      <keep-alive :include="$store.state.tabs.pageNames">
        <router-view></router-view>
      </keep-alive>
    </div>
  </div>
</template>

<script>
export default {
     
  methods: {
     
    handleAddPage(pageName) {
     
      this.$store.commit('tabs/addPage', pageName)
    },
    handleRemovePage(pageName) {
     
      this.$store.commit('tabs/removePage', pageName)
    }
  }
}
</script>

以上。

相关推荐

  1. Vue中的 keep-alive 实现原理

    2024-01-25 04:42:01       47 阅读
  2. vue2中的 <keep-alive

    2024-01-25 04:42:01       12 阅读
  3. vuekeep-alive的用法含义

    2024-01-25 04:42:01       14 阅读
  4. Vue-组件缓存-keep-alive

    2024-01-25 04:42:01       34 阅读
  5. vuekeep-alive 详解

    2024-01-25 04:42:01       12 阅读
  6. 前端基础 keep-alive的使用(Vue)

    2024-01-25 04:42:01       42 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-25 04:42:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-25 04:42:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-25 04:42:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-25 04:42:01       20 阅读

热门阅读

  1. springboot切面怎么将参数修改后传给目标方法

    2024-01-25 04:42:01       35 阅读
  2. Golang 定时任务的几种实现方法

    2024-01-25 04:42:01       34 阅读
  3. C# 实现 Vigenere 密码

    2024-01-25 04:42:01       27 阅读
  4. C++拾遗(四)引用与指针

    2024-01-25 04:42:01       27 阅读
  5. ROS学习笔记10——自定义源文件调用

    2024-01-25 04:42:01       35 阅读
  6. springboot集成mybatis处理json类型

    2024-01-25 04:42:01       35 阅读
  7. 汽车数据解决方案:通过更好的数据提高速度

    2024-01-25 04:42:01       32 阅读
  8. c语言之goto语句

    2024-01-25 04:42:01       34 阅读