动态组件
通过向 <component>
元素传递 is
属性,动态组件实现了组件之间动态切换的能力。
我们通过一个例子来快速了解一下动态组件的工作方式。假设我们有三个独立的组件,它们的标题为 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
对象中引用的是具体的组件,而不是组件名称。
译注
这里的 tabs
的定义使用了 JavaScript 的属性简写特性。tabs
的 key 是组件名字符串,value 是组件。这样写是为了后面通过 currentTab
,使用组件名来获取组件。
在 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>
这时我们就有了三个选项卡按钮了。
为了动态地渲染子元素,我们需要向 <component>
元素绑定一个 is
属性。传递给 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 />
元素放到我们的模板中时,我们注意到随着选项卡的切换,子组件动态地加载和卸载了。
保存状态
状态的保存是使用动态组件时一个很重要的考虑因素。默认情况下,当一个组件卸载时,它的状态也会丢失。然而,Vue 提供了一种在组件动态切换时也能保留状态的方法:<KeepAlive>
组件。
要保存动态组件中的状态,我们可以使用 <KeepAlive>
组件将 <component>
元素包裹起来。
<template>
<div class="demo">
<!-- -->
<KeepAlive>
<component :is="tabs[currentTab]" class="tab"></component>
</KeepAlive>
</div>
</template>
<script setup>
// ...
</script>
当 <component>
被 <KeepAlive>
包裹时,动态组件的状态将会在它们被卸载时保留下来。这意味着数据或组件状态将被保留,并且在组件重新挂载时将会恢复之前的状态。
我们修改之前的子组件,为它们添加一个简单的计数器。
<!-- 在 Home、Feed、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>
组件,我们可以通过保留组件状态来增强动态组件的功能,并在选项卡切换时提供更流畅的用户体验。