设计模式
动态组件
动态组件是指通过将 is
属性绑定到保留的 <component>
元素来动态更改(即切换)组件的能力。
我们将通过一个示例来更好地理解动态组件的工作原理。假设我们有三个名为 Home
、Feed
和 History
的独立组件,它们只是显示文本以指示它们是什么组件。
<!-- Home -->
<template><div class="tab">Home component</div></template>
<!-- Feed -->
<template><div class="tab">Feed component</div></template>
<!-- History -->
<template><div class="tab">History component</div></template>
我们的目标是构建一个界面,该界面显示一个可以点击的选项卡列表。根据点击的选项卡,我们希望动态渲染特定的组件。
在选项卡之间点击时,我们希望组件在不使用路由的情况下动态卸载和安装。虽然可以通过使用诸如 v-if
和 v-else
之类的指令有条件地渲染子模板来实现类似的功能,但这正是 Vue 动态组件的完美用例。
在应用程序的父级 App
组件中,我们可以先导入三个单独的组件,以便它们在模板中可用。我们还将创建一个 currentTab
响应式属性,该属性被赋予初始值为 "Home"
。
<script setup>
import { ref } from "vue";
import Home from "./components/Home.vue";
import Feed from "./components/Feed.vue";
import History from "./components/History.vue";
const currentTab = ref("Home");
const tabs = {
Home,
Feed,
History,
};
</script>
请注意,我们的 tabs
对象引用的是实际的组件定义,而不仅仅是组件名称。
在 App
组件模板中,我们将尝试渲染三个单独的选项卡按钮 - 每个按钮对应一个我们想要显示的组件。我们将使用 v-for
指令 来帮助实现这一点。我们将遍历 tabs
列表并渲染一系列 <button />
元素。对于每个渲染的 <button />
,我们将选项卡值绑定到元素的 key
属性,如果选项卡被选中/激活,则动态渲染 .active
类,并有一个点击处理程序,在选项卡被选中时更新组件 currentTab
值。
<template>
<div class="demo">
<button
v-for="(_, tab) in tabs"
:key="tab"
:class="['tab-button', { active: currentTab === tab }]"
@click="currentTab = tab"
>
{{ tab }}
</button>
</div>
</template>
<script setup>
import { ref } from "vue";
import Home from "./components/Home.vue";
import Feed from "./components/Feed.vue";
import History from "./components/History.vue";
const currentTab = ref("Home");
const tabs = {
Home,
Feed,
History,
};
</script>
通过这些更改,我们现在将看到三个选项卡按钮。
要动态渲染特定的子组件,我们将把 is
属性绑定到保留的 <component>
元素。附加到 is
属性的值应与我们想要动态渲染的子组件相对应。在本例中,我们将使用 currentTab
数据属性来指示在特定时间选中哪个子组件。
<template>
<div class="demo">
<button
v-for="(_, tab) in tabs"
:key="tab"
:class="['tab-button', { active: currentTab === tab }]"
@click="currentTab = tab"
>
{{ tab }}
</button>
<component :is="tabs[currentTab]" class="tab"></component>
</div>
</template>
<script setup>
import { ref } from "vue";
import Home from "./components/Home.vue";
import Feed from "./components/Feed.vue";
import History from "./components/History.vue";
// eslint-disable-next-line no-unused-vars
const currentTab = ref("Home");
// eslint-disable-next-line no-unused-vars
const tabs = {
Home,
Feed,
History,
};
</script>
在我们的模板中放置了动态 <component />
元素后,我们会注意到子组件现在会根据选中的选项卡动态卸载和安装。
1<template>2 <div class="demo">3 <button4 v-for="(_, tab) in tabs"5 :key="tab"6 :class="['tab-button', { active: currentTab === tab }]"7 @click="currentTab = tab"8 >9 {{ tab }}10 </button>11 <component :is="tabs[currentTab]" class="tab"></component>12 </div>13</template>1415<script setup>16import { ref } from "vue";17import Home from "./components/Home.vue";18import Feed from "./components/Feed.vue";19import History from "./components/History.vue";2021const currentTab = ref("Home");22const tabs = {23 Home,24 Feed,25 History,26};27</script>
保存状态
保存状态是使用动态组件时需要考虑的一个重要因素。默认情况下,当组件卸载时,其状态会丢失。但是,Vue 提供了一种使用 <KeepAlive>
组件保存动态组件状态的方法。
要保存动态组件的状态,我们可以将 <component>
元素用 <KeepAlive
> 组件包装起来。
<template>
<div class="demo">
<!-- -->
<KeepAlive>
<component :is="tabs[currentTab]" class="tab"></component>
</KeepAlive>
</div>
</template>
<script setup>
// ...
</script>
使用 <KeepAlive>
组件包装 <component>
元素后,动态组件的状态将在它们卸载时被保存。这意味着任何数据或组件状态将被保留,并且该组件在再次安装时将保留其先前状态。
要查看一个示例,我们可以更新每个子组件以包含一个简单的计数器,该计数器会递增。
<!-- Repeat this counter example for Home, Feed, and History -->
<template>
<div class="tab">
Home component
<p>Counter: {{ counter }}</p>
<button @click="incrementCounter">Increment</button>
</div>
</template>
<script setup>
import { ref } from "vue";
const counter = ref(0);
// eslint-disable-next-line no-unused-vars
const incrementCounter = () => {
counter.value++;
};
</script>
通过这些更改,我们会注意到每个子组件的计数器状态即使我们在组件之间动态切换也会被保留。
通过使用 <KeepAlive>
组件,我们可以通过保存动态组件的状态并为在选项卡之间切换提供更流畅的用户体验来增强动态组件的行为。
1<template>2 <div class="demo">3 <button4 v-for="(_, tab) in tabs"5 :key="tab"6 :class="['tab-button', { active: currentTab === tab }]"7 @click="currentTab = tab"8 >9 {{ tab }}10 </button>11 <KeepAlive>12 <component :is="tabs[currentTab]" class="tab"></component>13 </KeepAlive>14 </div>15</template>1617<script setup>18import { ref } from "vue";19import Home from "./components/Home.vue";20import Feed from "./components/Feed.vue";21import History from "./components/History.vue";2223const currentTab = ref("Home");24const tabs = {25 Home,26 Feed,27 History,28};29</script>