首页 前端知识 Vue 3 的 keep-alive 及生命周期钩子

Vue 3 的 keep-alive 及生命周期钩子

2025-03-11 15:03:00 前端知识 前端哥 248 452 我要收藏

在 Vue 3 中,keep-alive 是一个内置组件,用于提高性能和减少不必要的组件销毁与重建。它与组件的生命周期紧密相关,特别是在动态组件和路由切换场景下,能够缓存组件的状态并避免重新渲染。 

而 onActivated 和 onDeactivated 是 Vue3 Composition API 提供的钩子函数,专门用于处理被 keep-alive 缓存组件的激活和销毁逻辑。

1. keep-alive

1.1 什么是 keep-alive?

在 Vue 中,keep-alive 是一个内置的高阶组件,用于缓存不再活跃的组件实例,使其在不再渲染时依然保留状态。通过,在需要频繁切换视图组件或动态组件时使用 keep-alive,这样可以避免频繁销毁和重新创建组件,从而提高性能。

eg:很多情况下,当在多个选项卡或页面之间切换时,可能会希望保留每个选项卡的状态,而不是每次切换时都销毁再重新加载它们。此时,keep-alive 就可以解决这个问题。

1.2 基本使用

keep-alive 可以包裹动态组件或页面,当使用时,它会缓存已渲染的组件,并将其状态保持在内存中。当组件再次被激活时,keep-alive 会复用该组件的实例,而不是重新创建一个新的组件。

🌰 子组件 A + B 类似

<template>
  <div>
    <h2>这是组件A</h2>
    <p>这是组件A的内容</p>
  </div>
</template>

<script>
export default {
  name: 'ComponentA',
  mounted() {
    console.log('ComponentA 被挂载');
  },
  unmounted() {
    console.log('ComponentA 被卸载');
  },
};
</script>

App. vue

<template>
  <div>
    <button @click="switchComponent">切换组件</button>
    <keep-alive>
      <component :is="currentComponent"></component>
    </keep-alive>
  </div>
</template>

<script>
import { defineComponent, ref } from 'vue';
import ComponentA from './components/ComponentA.vue';
import ComponentB from './components/ComponentB.vue';

export default defineComponent({
  name: 'App',
  setup() {
    // 当前渲染的组件,直接引用组件
    const currentComponent = ref(ComponentA);

    // 切换组件的逻辑
    const switchComponent = () => {
      currentComponent.value = JSON.stringify(currentComponent.value) === JSON.stringify(ComponentA) ? ComponentB : ComponentA;
    };

    return {
      currentComponent,
      switchComponent,
    };
  },
});
</script>

展示如下:

点击切换

切换后,A 组件被缓存而不是被销毁,再次切换回来 B 组件被缓存,A 组件重新渲染。

如果不使用 keep-alive 表现为:

<template>
  <div>
    <button @click="switchComponent">切换组件</button>
    <component :is="currentComponent"></component>
  </div>
</template>

组件先被销毁然后创建。 

小 Tip 1

下面写法展示组件失败,为什么呢?

根本原因是,在 setup() 中使用的是字符串 'ComponentA' 和 'ComponentB',而在 keep-alive 和 component 组件的 :is 属性中,Vue 期望的是组件本身,而不是它的字符串名称。

因此需要将 currentComponent 的值设置为 组件的引用。

export default defineComponent({
  name: 'App',
  setup() {
    // 当前渲染的组件
    const currentComponent = ref('ComponentA');
    // 切换组件的逻辑
    const switchComponent = () => {
      currentComponent.value = currentComponent.value === 'ComponentA' ? 'ComponentB' : 'ComponentA';
    };
    return {
      currentComponent,
      switchComponent,
    };
  },
});
小 Tip 2

上面图片右侧控制台出现黄色警告⚠️,说明在 ref 中存储了一个 Vue 组件实例的响应式对象,而 Vue 会自动将其变成响应式,这可能会带来性能问题,为了避免这个情况,Vue 建议使用 markRaw 或 shallowRef 避免组件变成响应式对象。

解决方法

1、使用 markRaw

markRaw 会阻止 Vue 对传入对象进行响应式处理,它是 Vue 提供的一个优化方法,适用于那些不需要响应式处理的对象(比如组件)。

// 使用 markRaw 来避免组件变成响应式
const currentComponent = ref(markRaw(ComponentA));

2、使用 shallowRef(推荐)

shallowRef 是 Vue 3 提供的一个 API,类似于 ref,但它只会让引用的对象本身变得响应式,而不会递归地将对象中的嵌套属性变成响应式。对于 Vue 组件,使用 shallowRef 会更加合适,因为我们只关心组件本身的引用,而不需要其内部的响应式。

// 使用 shallowRef 来避免组件变成深度响应式
const currentComponent = shallowRef(ComponentA);

1.3 include 和 exclude 属性

keep-alive 还提供了 include 和 exclude 属性,用于控制哪些组件应该被缓存,哪些组件不应该被缓存。

- include:一个字符串、正则表达式或数组,指定哪些组件应该被缓存。

- exclude:一个字符串、正则表达式或数组,指定哪些组件不应该被缓存。

🌰

<template>
  <div>
    <button @click="switchComponent">切换组件</button>
    <keep-alive :include="['ComponentA']" :exclude="['ComponentB']">
      <component :is="currentComponent"></component>
    </keep-alive>
  </div>
</template>
<script>
import { defineComponent, shallowRef } from 'vue';
import ComponentA from './components/ComponentA.vue';
import ComponentB from './components/ComponentB.vue';

export default defineComponent({
  name: 'App',
  setup() {
    const currentComponent = shallowRef(ComponentA);
    const switchComponent = () => {
      currentComponent.value = JSON.stringify(currentComponent.value) === JSON.stringify(ComponentA) ? ComponentB : ComponentA;
    };
    return {
      currentComponent,
      switchComponent,
    };
  },
});
</script>

只有 ComponentA 会被缓存,ComponentB 不会。根据实际需要灵活控制哪些组件需要被缓存。

2. 钩子函数

2.1 onActivated

onActivated 钩子会在组件被缓存并重新激活时触发。通常情况下,组件被 keep-alive 缓存后,如果用户切换到该组件,Vue 会复用这个组件的实例并调用 onActivated 钩子。

使用场景:需要在组件激活时重新加载数据、执行某些操作(eg:开启动画、更新状态等)。

2.2 onDeactivated

与 onActivated 类似,onDeactivated 会在组件被缓存并从 keep-alive 中移除时触发。此时,组件的状态会被保留,但它的 DOM 和实例会被销毁,当再次激活时,onActivated 会重新触发。

使用场景:需要在组件去激活时清理资源,例如停止计时器、清理事件监听器等。或进行一些状态重置操作。

2.3 🌰

注意:这两个钩子函数只有在组件被 <keep-alive> 包裹,且组件经历过激活和停用的过程,也就是组件需要在显示和隐藏之间切换,而不是一直保持显示状态。

子组件

<template>
  <div>
    <h3>子组件</h3>
  </div>
</template>

<script setup>
import { onActivated, onDeactivated } from 'vue';

// 在组件激活时重新加载数据
onActivated(() => {
  console.log('组件被激活,重新加载数据');
});

// 在组件停用时清理数据
onDeactivated(() => {
  console.log('组件被停用,清理数据或停止操作');
});
</script>

App.vue

<template>
  <div>
    <keep-alive>
      <Info v-if="isActive" />
    </keep-alive>
    <button @click="toggleActive">切换显示</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import Info from './components/Info.vue';

const isActive = ref(true);

const toggleActive = () => {
  isActive.value = !isActive.value;
};
</script>

展示为:

小 Tip 3 

上面🌰使用 v-if 控制组件的显示和隐藏,如果使用 v-show 呢?

<template>
  <div>
    <button @click="toggleActive">切换组件显示</button>
    <keep-alive>
      <UserInfo v-show="isActive" />
    </keep-alive>
  </div>
</template>

结果是:不会触发。

如果使用 v-show 控制组件显示和隐藏,不会触发两个钩子的执行,因为其是通过修改 display 属性来显示或隐藏 DOM 元素,而不会销毁和重新渲染组件。

2.4 存在问题 

1、缓存时的副作用

当组件被缓存时,它的状态会保留在内存中,不会被销毁。如果需要清理某些副作用(例如,清理计时器、停止网络请求等),应该在 onDeactivated 中进行处理。

onDeactivated(() => {
  // 停止任何异步任务或计时器
  clearInterval(someTimer)
  console.log('停止异步任务')
})

2、性能问题

如果 onActivated 钩子执行了某些耗时操作(如数据请求、定时任务等),可能会影响用户体验。为了避免影响性能,可以在 onActivated 中进行懒加载操作,只在需要时执行某些任务。

onActivated(() => {
  if (!info.value) {
    fetchData()  // 如果没有数据才加载
  }
})

3. keep-alive 与路由结合 

3.1 在 Vue 路由中使用 keep-alive

Vue 路由通常与 keep-alive 一起使用,特别是在单页面应用(SPA)中。当切换路由时,希望某些页面保持活跃并缓存其状态。keep-alive 可以用来缓存路由视图组件,避免在路由切换时销毁和重新渲染。

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

<script>
export default {
  // 使用 keep-alive 包裹 router-view,使得路由切换时组件能够缓存
};
</script>

router-view 是 Vue 路由用于渲染当前匹配的路由组件,keep-alive 缓存路由组件,当路由切换时,之前的组件不会被销毁,而是保持活跃并缓存状态。

3.2 动态路由

keep-alive 的 include 和 exclude 属性可以用来动态控制哪些路由视图组件应该被缓存,哪些不应该被缓存。

<template>
  <div>
    <h1>动态路由和 keep-alive</h1>
    <keep-alive :include="['Home', 'About']">
      <router-view></router-view>
    </keep-alive>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 只有 Home 和 About 组件会被缓存
    }
  },
}
</script>

路由配置:

import { createRouter, createWebHistory } from 'vue-router';
import Home from './components/Home.vue';
import About from './components/About.vue';
import Contact from './components/Contact.vue';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home,
  },
  {
    path: '/about',
    name: 'About',
    component: About,
  },
  {
    path: '/contact',
    name: 'Contact',
    component: Contact,
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

同理:

<keep-alive :exclude="['Contact']">
  <router-view></router-view>
</keep-alive>

根据具体情况来控制缓存策略,从而有效提升性能,并节省内存。

总结:使用 keep-alive 时,路由切换时被缓存的组件会保持其状态。例如,表单输入的数据、滚动位置等都不会丢失。为了确保在缓存组件时能保持状态,可以使用 onActivated 和 onDeactivated 钩子函数执行一些操作。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/23303.html
标签
评论
发布的文章

面试题之强缓存协商缓存

2025-03-11 15:03:21

【C语言】数组篇

2025-03-11 15:03:19

正则表达式(复习)

2025-03-11 15:03:17

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!