Skip to content

动态组件 & KeepAlive

动态组件

在某些开发场景下,我们需要在多个组件之间来回切换,比如 Tab 页面:

dark

为了实现多个组件之间的互相切换,除了可以使用 v-if 指令之外,还可以通过 Vue 内置的 <component> 元素和 is attribute 来实现。

其语法格式如下:

html
<component is="组件名/组件实例"></component>

其中:

  • <component> 元素是组件的占位符;
  • is 属性用来指定把哪个组件渲染到 <component> 占位符中;

基于组件名动态渲染组件

<script setup> 中,必须使用 defineOptions() 宏函数,再配合选项式 API components 来给导入的组件注册一个字符串的名称:

vue
<script setup>
// 1. 导入组件
import Home from './Home.vue'
import Movie from './Movie.vue'
import About from './About.vue'
import { ref } from 'vue'

defineOptions({
  // 2. 使用选项式 API 为导入的组件提供一个注册的名称
  components: {
    home: Home,
    movie: Movie,
    about: About
  }
})

// 3. 定义响应式数据,存储要渲染到 component 元素中的组件的名字
const active = ref('home')
</script>

<template>
  <!-- 4. 点击按钮,切换要展示的组件的名称 -->
  <button @click="active = 'home'">首页</button>
  <button @click="active = 'movie'">电影</button>
  <button @click="active = 'about'">关于</button>
  <hr />

  <!-- 5. 动态展示组件 -->
  <component :is="active"></component>
</template>

基于组件实例动态渲染组件

根据组件名动态切换要展示的组件比较麻烦,因为还必须配合选项式 API 进行组件名的注册。为此,我们还可以直接基于组件实例的数组 + 索引来实现组件的动态渲染:

vue
<script setup>
// 1. 导入组件
import Home from './Home.vue'
import Movie from './Movie.vue'
import About from './About.vue'
import { ref } from 'vue'

// 2. 把需要切换显示的“组件实例”放到一个数组中
const tabs = [Home, Movie, About]
// 3. 定义响应式数据,存储要渲染到 component 元素中的组件的“索引”
const activeIndex = ref(0)
</script>

<template>
  <!-- 4. 点击按钮,切换要展示的组件的索引 -->
  <button @click="activeIndex = 0">首页</button>
  <button @click="activeIndex = 1">电影</button>
  <button @click="activeIndex = 2">关于</button>
  <hr />

  <!-- 5. 根据索引,动态展示组件 -->
  <component :is="tabs[activeIndex]"></component>
</template>

把 is 属性应用于原生 DOM

当把 is 属性应用于原生 DOM 元素时,可用来把原生的 DOM 元素替换为指定的 Vue 组件。其语法格式为:

html
<div is="vue:连字符格式的组件名"></div>

is 属性中可以加上 vue: 前缀,表示把该 div 元素替换渲染为指定的 Vue 组件:

vue
<script setup>
// 1. 导入组件并命名为 MyTabs
import MyTabs from './components2/Tabs.vue'
</script>

<template>
  <!-- vue: 后面的组件名,需要采用“连字符”的形式 -->
  <div is="vue:my-tabs"></div>
</template>

KeepAlive

在使用 <component> 动态切换组件时,被替换掉的组件会被销毁,被展示出来的组件会被重新初始化和创建。因此每次发生组件切换展示时,都会导致组件已有状态的丢失

例如,修改 Home.vue 组件如下:

vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const count = ref(0)
onMounted(() => console.log('Home 组件挂载完成!'))
onUnmounted(() => console.log('Home 组件卸载完成!'))
</script>

<template>
  <h3>Home 页面 -- {{ count }}</h3>
  <button @click="count++">+1</button>
</template>

TIP

结合 console 面板和 Vue 调试工具,我们可以明显的发现 Home 组件每次都被销毁和重新创建!

基本用法

为了实现组件状态的保持(被替换的组件被缓存而非销毁),Vue 内置提供了 <KeepAlive> 组件。我们可以使用 <KeepAlive> 组件把 <component> 渲染的动态组件包裹起来:

html
<!-- 非活跃的组件将会被缓存! -->
<KeepAlive>
  <component :is="tabs[activeIndex]"></component>
</KeepAlive>

结合 console 面板和 Vue 调试工具,我们发现被替换掉的组件没有被销毁,而是被缓存了起来。当被替换掉的组件需要再次被展示的时候,也没有重新创建这个组件,而是把它从缓存状态唤醒为激活状态

缓存实例的生命周期

<KeepAlive> 缓存的组件,会额外多出两个生命周期函数,分别是 onActivatedonDeactivated。它们的调用时机如下所示:

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

// 1. 组件变为:激活态
onActivated(() => {
  // 调用时机为首次挂载
  // 以及每次从缓存中被重新插入时
})

// 2. 组件变为:缓存态
onDeactivated(() => {
  // 在从 DOM 上移除、进入缓存
  // 以及组件卸载时调用
})
</script>

例如:在 Home 组件中使用这两个生命周期函数:

vue
<script setup>
import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
const count = ref(0)
onMounted(() => console.log('Home 组件挂载完成!'))
onUnmounted(() => console.log('Home 组件卸载完成!'))

// 组件缓存相关的生命周期函数
onActivated(() => {
  console.log('组件挂载/激活了')
})
onDeactivated(() => {
  console.log('组件卸载/被缓存了')
})
</script>

<template>
  <h3>Home 页面 -- {{ count }}</h3>
  <button @click="count++">+1</button>
</template>

最大缓存实例数

我们可以给 <KeepAlive> 传入 max prop,来控制最多可缓存多少个组件。当前我们总共有 Home、Movie 和 About 三个组件,如果我们把 max 设置为 2,则最多只会有两个组件被缓存:

html
<KeepAlive :max="2">
  <component :is="tabs[activeIndex]"></component>
</KeepAlive>

假设我们先访问了 Home,又访问了 Movie 组件,此时已经达到了最大缓存数。

当我们访问 About 组件时,<KeepAlive> 会先把最久没有被访问的 Home 组件销毁,再把 About 加入缓存。

此时,被缓存的组件是 MovieAbout 组件,当前最久没有被访问的组件是 Movie


如果我们又访问了 Movie 组件,则当前被缓存的组件依然是 MovieAbout,不过最久没有被访问的组件变成了 About

当下一次我们访问 Home 组件时,<KeepAlive> 会把 About 组件销毁,再新建 Home 组件并把它加入缓存。

包含/排除

默认情况下 <KeepAlive> 会缓存内部所有的组件实例。我们可以使用 includeexclude prop 来指定哪些组件被缓存哪些组件不需要被缓存

这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组

html
<!-- 以英文逗号分隔的字符串, -->
<!-- 注意:这里的英文逗号的前后“不能”有空格 -->
<KeepAlive include="Home,About">
  <component :is="tabs[activeIndex]"></component>
</KeepAlive>

<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/Home|About/">
  <component :is="tabs[activeIndex]"></component>
</KeepAlive>

<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['Home', /Movie|About/]">
  <component :is="tabs[activeIndex]"></component>
</KeepAlive>

includeexclude 会根据组件的 name 名称进行匹配。在使用了 <script setup> 的单文件组件中,会自动根据文件名生成对应的 name 选项,无需再手动声明。

TIP

但是,必须在组件内声明 <script setup> 并编写相应的逻辑。否则就只能使用 defineOptions({ name: '组件名' }) 来显示给出组件名。

天不生夫子,万古长如夜