2025-08-04 vue3 keep-alive相关

大部分摘抄至https://juejin.cn/post/7511568225987051555?searchId=202508041118462C6329C8A295609FB176#heading-36

keep-alive

<template>
  <keep-alive :include="['ComponentA', 'ComponentB']" :max="5">
    <component :is="currentComponent"></component>
  </keep-alive>
</template>

一、使用场景和核心作用

1.作用:

1.1 缓存组件实例 ,避免重复销毁和创建,保留组件状态(如DOM结构、响应式数据、事件监听)

  • (Vue 等前端框架中,组件实例(Component Instance) 是指组件被渲染到页面时,框架根据组件定义(如模板、数据、方法等)创建的一个具体运行时对象,它包含了组件运行所需的所有信息和状态,是组件在页面中 “存活” 的具体形态

  • 可以简单理解为:组件定义是 “设计图纸”,而组件实例是根据图纸造出的 “实物”,这个实物拥有自己的属性、行为和生命周期。

  • 组件实例包含的核心内容:DOM 结构,响应式数据data、props....,方法与时间监听methods、watch、@click...,生命周期状态)

1.2 提升性能:适用于需要频繁切换但状态需保留的组件(如Tab页、表单填写页)

场景: tab 切换,表单填写页

二、生命周期钩子变化

  1. 新增钩子(仅在被缓存的组件中触发)
    onActivated:组件被激活(插入DOM)时触发。
    onDeactivated:组件被停用(移除DOM)时触发
  2. 执行顺序:
    首次加载:onCreate->onMounted->onActivated
    切换离开:onDeactivated
    再次进入:onActivated
    彻底销毁:onUnmounted

三、关键配置属性

1.include
匹配组件名称(name选项),仅缓存匹配的组件
支持字符串、正则、数组

<keep-alive :include="/^Test/">  

2.exclude

排除指定组件,优先级高于include

3.max

最大缓存实例数,超出时按LRU(最近最少使用)策略淘汰旧实例
LUR原理:有限淘汰最久未访问的实例

四、高频面试题

keep-alive实现原理

1、缓存机制:通过Map或Object缓存组件vnode实例,渲染时直接从缓存中取


image.png

1.1使用 Object 缓存组件
通过对象的键值对特性,将组件实例存储在对象中:

import { defineComponent, h, ref } from 'vue'

export default defineComponent({
  setup() {
    // 用于缓存组件的对象
    const componentCache = {}
    
    // 需要渲染的组件列表
    const components = ref([
      { id: 'comp1', type: 'ComponentA', props: { msg: '组件1' } },
      { id: 'comp2', type: 'ComponentB', props: { msg: '组件2' } }
    ])
    
    // 获取或创建组件实例
    const getComponent = (item) => {
      // 如果缓存中存在,直接返回
      if (!componentCache[item.id]) {
        // 根据类型动态导入组件
        const Component = item.type === 'ComponentA' 
          ? defineComponent({
              props: ['msg'],
              render() { return h('div', this.msg) }
            })
          : defineComponent({
              props: ['msg'],
              render() { return h('div', this.msg + ' (B类型)') }
            })
        
        // 创建组件实例并缓存
        componentCache[item.id] = h(Component, { ...item.props })
      }
      return componentCache[item.id]
    }
    
    return () => h('div', components.value.map(item => getComponent(item)))
  }
})

1.2使用 Map 缓存组件
Map 相比 Object 更适合作为缓存容器,因为它支持各种类型的键,且有更丰富的操作方法:

import { defineComponent, h, ref } from 'vue'

export default defineComponent({
  setup() {
    // 用于缓存组件的Map
    const componentCache = new Map()
    
    // 需要渲染的组件列表
    const components = ref([
      { id: 'comp1', type: 'ComponentA', props: { msg: '组件1' } },
      { id: 'comp2', type: 'ComponentB', props: { msg: '组件2' } }
    ])
    
    // 获取或创建组件实例
    const getComponent = (item) => {
      // 使用id作为Map的键
      if (!componentCache.has(item.id)) {
        // 动态创建组件
        const Component = item.type === 'ComponentA' 
          ? defineComponent({
              props: ['msg'],
              render() { return h('div', this.msg) }
            })
          : defineComponent({
              props: ['msg'],
              render() { return h('div', this.msg + ' (B类型)') }
            })
        
        // 缓存组件实例
        componentCache.set(item.id, h(Component, { ...item.props }))
      }
      return componentCache.get(item.id)
    }
    
    return () => h('div', components.value.map(item => getComponent(item)))
  }
})

两者区别

  • 键类型支持:
    Map:键可以是任意 JavaScript 类型,包括对象、函数等。例如,可以使用组件实例本身或其他复杂对象作为键来缓存相关组件,这在需要更灵活的键值关联场景中非常有用。
    Object:键只能是字符串类型和 Symbol 类型。如果需要以非字符串或 Symbol 类型作为键来缓存组件,就无法直接使用 Object。
  • 插入顺序保留:
    Map:严格保留键值对的插入顺序。当迭代 Map 时,会按照插入的顺序返回键值对,这对于需要按照特定顺序处理缓存组件的场景很重要,比如依次渲染缓存的组件。
    Object:在 ES6 之后,对象的键也是有序的,但在早期 JavaScript 实现中,对象键的顺序是不可靠的。因此,如果依赖键的顺序,使用 Object 缓存组件可能会出现意外情况。
  • 属性数量获取:
    Map:可以通过.size 属性直接获取其中键值对的数量,操作简单高效。
    Object:需要通过手动计算,如使用 Object.keys (obj).length 来获取对象中属性的数量,相对而言更麻烦一些。
  • 原型链污染风险:
    Map:Map 实例是纯净的键值存储,不存在原型链污染的风险,它与原型链完全隔离,不会受到原型对象属性的影响。
    Object:对象可能会继承原型链上的属性,存在原型链污染的风险。如果不小心修改了原型对象的属性,可能会影响到所有继承该原型的对象,对于缓存组件来说,这可能导致意外的行为。
  • 性能表现:
    Map:在频繁增删键值对的场景下,Map 的表现更好,因为其内部结构针对这种操作进行了优化。例如,当组件频繁地被添加到缓存或从缓存中移除时,Map 能提供更高效的性能。
    Object:在频繁访问已知属性的场景下,Object 表现更快。但如果是大量的增删操作,Object 的性能会较差,因为它没有针对这种情况做特殊优化。
  • 迭代方式:
    Map:直接支持 for...of 遍历,并且提供了 entries ()、keys ()、values () 等迭代器方法,方便进行各种形式的迭代操作。
    Object:需要先通过 Object.keys ()、Object.values () 等方法将对象的键或值转换为数组,然后才能进行迭代,操作步骤相对较多。

2、DOM处理:
该组件实例对应的整个 DOM 树会被从真实的文档流 (DOM tree) 中完全移除 (detached) 。这就是为什么在页面检查器里看不到它的 DOM 了。
当这个组件再次被激活时,keep-alive 会从缓存中找到这个实例,直接复用这个组件实例(保留所有状态),并将它对应的 DOM 树重新插入 (attached) 到文档流中。

如何动态控制组件缓存

方案1:绑定动态include/exclude(响应式变量)

<keep-alive :include="cachedComponents">

方案2:通过key强制重新渲染(改变key会销毁旧实例)

<component :is="currentComponent" :key="componentKey">

keep-alive如何结合路由使用

搭配router-view

<router-view v-slot="{ Component }">
  <keep-alive>
    <component :is="Component" v-if="$route.meta.keepAlive" />
  </keep-alive>
  <component :is="Component" v-if="!$route.meta.keepAlive" />
</router-view>

路由配置:通过meta字段标记需缓存的页面

{ path: '/home', component: Home, meta: { keepAlive: true } }

缓存组件如何更新数据

onActivated中刷新数据

onActivated(() => {
  fetchData(); // 重新请求数据
});

max属性的作用及淘汰策略

作用:避免内存无限增长,限制最大缓存实例
淘汰策略:LRU(最近最少使用),优先移除最久未被访问的实例

注意事项

组件必须设置name选项:否则include/exclude无法匹配
避免内存泄漏:及时清理不需要缓存的组件(如通过max或动态include)
SSR不兼容:keep-alive仅在客户端渲染中生效
缓存组件的状态保留:表单内容等会被保留,需手动重置或通过key强制更新

实战实例

<template>
  <button @click="toggleComponent">切换组件</button>
  <keep-alive :include="cachedComponents" :max="3">
    <component :is="currentComponent" :key="currentComponent" />
  </keep-alive>
</template>

<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

const currentComponent = ref('ComponentA');
const cachedComponents = ref(['ComponentA', 'ComponentB']);

const toggleComponent = () => {
  currentComponent.value = currentComponent.value === 'ComponentA' ? 'ComponentB' : 'ComponentA';
};
</script>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。