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

为了实现多个组件之间的互相切换,除了可以使用 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>
缓存的组件,会额外多出两个生命周期函数,分别是 onActivated
和 onDeactivated
。它们的调用时机如下所示:
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
加入缓存。
此时,被缓存的组件是 Movie
和 About
组件,当前最久没有被访问的组件是 Movie
。
如果我们又访问了 Movie
组件,则当前被缓存的组件依然是 Movie
和 About
,不过最久没有被访问的组件变成了 About
。
当下一次我们访问 Home 组件时,<KeepAlive>
会把 About
组件销毁,再新建 Home
组件并把它加入缓存。
包含/排除
默认情况下 <KeepAlive>
会缓存内部所有的组件实例。我们可以使用 include
和 exclude
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>
include
或 exclude
会根据组件的 name
名称进行匹配。在使用了 <script setup>
的单文件组件中,会自动根据文件名生成对应的 name
选项,无需再手动声明。
TIP
但是,必须在组件内声明 <script setup>
并编写相应的逻辑。否则就只能使用 defineOptions({ name: '组件名' })
来显示给出组件名。