vue3手动删除keepAlive缓存的方法

 更新时间:2024年03月21日 08:24:37   作者:yanessa_yu  
当我们未设置keepAlive的最大缓存数时,当缓存组件太多,会导致内存溢出,本文给大家介绍了vue3手动删除keepAlive缓存的方法,文中通过代码示例介绍的非常详细,需要的朋友可以参考下

keepAlive的最大缓存数是无限大

当我们未设置keepAlive的最大缓存数时,当缓存组件太多,会导致内存溢出。

keepAlive最大缓存数测试实践

  • 如下脚本,给组件设置keepAlive缓存,并将组件的key设置为$route.path
 <router-view v-slot="{ Component }">
    <keep-alive >
      <component :is="Component" :key="$route.path"/>
    </keep-alive>
  </router-view>
  • 如下脚本,设置一个动态路由,不同id共用同一个组件
{
      path: '/el/cascader/:id',
      name: 'ElCascaderDemo2',
      component: () => import('../views/ElCascaderDemo.vue')
    },
  • 如下脚本, 点击每一行,可以不断的新增缓存组件
<script setup lang="ts">
 import { useRouter } from 'vue-router'
 const router = useRouter()
  const go = (id: number) => {
    router.push({
      path: '/el/cascader/' + id
    })
  }
</script>

<template>
  <main>
    <div v-for="i in 10000" @click="go(i)" class="item">
      {{  i  }}
    </div>
  </main>
</template>
  • 下面脚本是keepAlive组件中的源码,在内部打断点,查看当前cache中缓存的组件数是多少。
const cacheSubtree = () => {
      if (pendingCacheKey != null) {
        cache.set(pendingCacheKey, getInnerChild(instance.subTree));
      }
    };
    onMounted(cacheSubtree);
    onUpdated(cacheSubtree);
  • 如下图,当cache中缓存12个组件时,内存已经达到了2G,

  • 如下图,在继续加压后,内存达到4019MB时,页面崩溃。

故在使用keepAlive缓存组件,一定要设置它的最大缓存数。

设置keepAlive最大缓存数

<router-view v-slot="{ Component }">
    <keep-alive :max="10">
      <component :is="Component" :key="$route.path"/>
    </keep-alive>
  </router-view>

设置最大缓存数等于10以后,keepAlive组件内缓存变量cache的size<=9,内存位置在1900MB。

实现手动删除缓存组件

实现手动删除缓存组件的方式是:动态增删keepAlive组件的exclude属性。exclude属性的值可以是一个组件名称组成的数组。在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,无需再手动声明。但目前组件的key等于route.path,/el/cascader/1/el/cascader/2会缓存两份,手动删除这类组件,需要给它们各自一个名称,而不是都用它们指向的那一个组件的名称。

第一步:封装动态组件component的is属性的赋值,使用route.path作为组件的名称。

 <router-view v-slot="{ Component }">
    <keep-alive :max="10">
      <component :is="formatComponentInstance(Component, $route?.path)" :key="$route.path"/>
    </keep-alive>
  </router-view>
  
  
let wrapperMap = new Map()
const formatComponentInstance = (component : Component, path:string ) => {
  let wrapper
  if (wrapperMap.has(path)) {
    wrapper = wrapperMap.get(path)
  } else {
    wrapper = {
      name: path,
      render(){
        return h(component) // h的第一个参数可以是字符串,也可以是一个组件定义;h返回的是一个虚拟dom
      }
    }
    wrapperMap.set(path, wrapper)
  }
  return h(wrapper)
}

第二步:实现一个简单内页签,内页签关闭时清除组件缓存

<template>
    <el-tag
      v-for="item in editableTabs"
      :key="item.key"
      closable
      :disable-transitions="false"
      @click="tabChange(item.key)"
      @close="handleTabsEdit(item.key, 'remove', undefined)"
      :type="editableKey === item.key ? 'primary':'info'"
    >
      {{ item.title }}
    </el-tag>
  </template>
  <script lang="ts" setup>
  import { ref, watch } from 'vue'
  import type { Ref } from 'vue'
  import { ElTag} from 'element-plus'
  import { useRoute, useRouter} from 'vue-router'
  const route = useRoute()
  const router = useRouter()
  import { useKeepAlive } from '../stores/index'

  let tabIndex = 1
  const editableKey = ref('')
  const editableTabs : Ref<Array<{[key: string]: any}>> = ref([])
  
  const visitedRoute: String [] = []
  watch(() => route.path, (val) => {
    if (visitedRoute.indexOf(val) === -1) {
        visitedRoute.push(val)
        handleTabsEdit(undefined, 'add', val)
    } else {
      editableKey.value = editableTabs.value.filter((tab) => tab.title === val)[0].key
    }
  })
  
  const handleTabsEdit = (
    targetKey: string | undefined,
    action: 'remove' | 'add',
    newTabTitle: string | undefined
  ) => { 
    if (action === 'add') {
      const newTabName = `${++tabIndex}`
      editableTabs.value.push({
        title: newTabTitle,
        key: newTabName,
      })
      editableKey.value = newTabName
    } else if (action === 'remove') {
      const tabs = editableTabs.value
      let activeKey = editableKey.value
      if (activeKey === targetKey) {
        tabs.forEach((tab, index) => {
          if (tab.key === targetKey) {
            //  同步删除visitedRoute数组
            let includeIndex = visitedRoute.indexOf(tab.title)
            visitedRoute.splice(includeIndex, 1)
            //    重置active tab
            const nextTab = tabs[index + 1] || tabs[index - 1]
            if (nextTab) {
              activeKey = nextTab.name
              router.push({
                path: nextTab.title
              })
            }
            // /页签关闭时,重置store中的exclude数组,如['/el/cascader/2']
            const keepAliveStore = useKeepAlive()
              let exclude: string[] = [tab.title]
              keepAliveStore.setExclude(exclude)
          }
        })
      }
  
      editableKey.value = activeKey
      editableTabs.value = tabs.filter((tab) => tab.key !== targetKey)
    }
  }

  const tabChange  = (activeKey: string) => {
    let path = editableTabs.value.filter((tab) => tab.key === activeKey)[0].title
    router.push({
        path
    })
  }
  </script>
  
  

const keepAliveStore = useKeepAlive()
const excludes = computed(() => {
  return keepAliveStore.exclude
})

<router-view v-slot="{ Component }">
    <keep-alive :max="10" :exclude="excludes">
      <component :is="formatComponentInstance(Component, $route?.path)" :key="$route.path"/>
    </keep-alive>
  </router-view>

在调试中出现以下报错:

Uncaught (in promise) TypeError: parentComponent.ctx.deactivate is not a function 报错的原因是因为我将上面的封装简化为以下脚本, 导致同一个path两次产生的组件vnode的type不一样。为什么要简化呢, 因为考虑到wrapperMap会有一定的内存消耗。

const formatComponentInstance = (component : Component, path:string ) => {
    let wrapper = {
      name: path,
      render(){
        return h(component) // h的第一个参数可以是字符串,也可以是一个组件定义;h返回的是一个虚拟dom
      }
    }
    return h(wrapper)
 }

在vue源码中的isSameVNodeType中的n1.type === n2.type的判断中, 组件转化的vnode的type是一个对象,对于相同的route.path, 每次通过上面的封装脚本产生新的组件实例,会导致每次产生的vnode的type对象不是同一个对象,导致n1.type不等于n2.type, isSameVNodeType返回false,会卸载n1,卸载时就产生了上面的报错。

function isSameVNodeType(n1, n2) {
  if (n2.shapeFlag & 6 && hmrDirtyComponents.has(n2.type)) {
    n1.shapeFlag &= ~256;
    n2.shapeFlag &= ~512;
    return false;
  }
  return n1.type === n2.type && n1.key === n2.key;
}

以上就是vue3手动删除keepAlive缓存的方法的详细内容,更多关于vue3删除keepAlive缓存的资料请关注脚本之家其它相关文章!

相关文章

  • vue实现分页功能

    vue实现分页功能

    这篇文章主要为大家详细介绍了vue实现分页功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • vue使用keep-alive后清除缓存的方法

    vue使用keep-alive后清除缓存的方法

    这篇文章主要给大家介绍了关于vue使用keep-alive后清除缓存的相关资料,这个问题在我们日常工作中经常会用到,本文通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-08-08
  • 一文详解websocket在vue2中的封装使用

    一文详解websocket在vue2中的封装使用

    这篇文章主要为大家介绍了一文详解websocket在vue2中的封装使用,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • 详解Vue的watch中的immediate与watch是什么意思

    详解Vue的watch中的immediate与watch是什么意思

    这篇文章主要介绍了详解Vue的watch中的immediate与watch是什么意思,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • vue axios基于常见业务场景的二次封装的实现

    vue axios基于常见业务场景的二次封装的实现

    这篇文章主要介绍了vue axios基于常见业务场景的二次封装的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • 栽Vue3中传递路由参数的三种方式

    栽Vue3中传递路由参数的三种方式

    vue 路由传参的使用场景一般都是应用在父路由跳转到子路由时,携带参数跳转,传参方式可划分为 params 传参和 query 传参,本文将给大家介绍如何通过不同方式在 Vue 3 中传递路由参数,需要的朋友可以参考下
    2024-07-07
  • Vue(定时器)解决mounted不能获取到data中的数据问题

    Vue(定时器)解决mounted不能获取到data中的数据问题

    这篇文章主要介绍了Vue(定时器)解决mounted不能获取到data中的数据问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • 利用Vue实现一个累加向上漂浮动画

    利用Vue实现一个累加向上漂浮动画

    在不久之前,看到一个比较有意思的小程序,就是静神木鱼,可以实现在线敲木鱼,自动敲木鱼,手盘佛珠,静心颂钵的,下面就来揭秘如何实现这个小程序中敲木鱼的累加向上漂浮动画,需要的可以参考一下
    2022-11-11
  • 前端Vue3项目打包成Docker镜像运行的详细步骤

    前端Vue3项目打包成Docker镜像运行的详细步骤

    将Vue3项目打包、编写Dockerfile、构建Docker镜像和运行容器是部署Vue3项目到Docker的主要步骤,这篇文章主要介绍了前端Vue3项目打包成Docker镜像运行的详细步骤,需要的朋友可以参考下
    2024-09-09
  • vue router 跳转后回到顶部的实例

    vue router 跳转后回到顶部的实例

    今天小编就为大家分享一篇vue router 跳转后回到顶部的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08

最新评论