Skip to content

动态组件:component

有些场景会需要在多个组件间来回切换,比如 Tab 界面:

1832671033857933312.png

那么使用动态组件无疑是个好的解决方案。

先把几个tab组件搞出来,代码都非常简单。

html
// ------------------- src/components/A.vue -------------------
<template>
    <div>A组件</div>
</template>
<script>
</script>
<style scoped>
</style>

// ------------------- src/components/B.vue -------------------
<template>
    <div>B组件</div>
    <input type="text">
</template>
<script>
</script>
<style scoped>
</style>

// ------------------- src/components/C.vue -------------------
<template>
    <div>C组件</div>
</template>
<script>
</script>
<style scoped>
</style>

然后其他文件不变,这里主要在App.vue中做演示:

html
<template>
    <div>
        <h1>动态组件</h1>
        <div>
            <button v-for="(item, index) in tableList" @click="change(index)">{{item.title}}</button>
        </div>
        <!--   动态组件使用标签component,其is的值是组件名,对应的组件就会渲染在这里    -->
        <!--   下面为了演示方便我做了个数据双向绑定    -->
        <component :is="currentComponent.com"></component>
    </div>
    
</template>
<script setup>
import A from './components/A.vue'
import B from './components/B.vue'
import C from './components/C.vue'
import {reactive, markRaw} from "vue";

// 关于markRaw,标记一个对象,使其永远不会转换为 proxy。返回对象本身。意思就是使其不能成为一个响应式对象。
// 跳过 proxy 转换可以提高性能。
const tableList = reactive([
        {title: "A组件", com: markRaw(A)},
        {title: "B组件", com: markRaw(B)},
        {title: "C组件", com: markRaw(C)},
])
const currentComponent = reactive({
    com:tableList[0].com
})
function change(index) {
    currentComponent.com = tableList[index].com
}
</script>
<style scoped>
</style>

上面代码运行后,就是点击对应的按钮,显示对应的组件,切换非常流畅。

默认情况下,一个组件实例在被替换掉后会被销毁。这会导致它丢失其中所有已变化的状态——当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。

例如你在B组件中的input框中输入数据的话,你会发现再次切换之后,这个输入的数据就消失了。这是因为每当切换组件时,上一个组件就会销毁,等下次切换时再加载,这就导致了之前的组件销毁时,其中input框的数据也就没了....

如果要保留输入的值,其实也有招,那就是切换时将组件缓存下来,不让其组件销毁就行了。这就用到了keep-alive组件了。

keep-alive

https://cn.vuejs.org/guide/built-ins/keep-alive.html

<KeepAlive> 是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。

html
<template>
    <div>
        <h1>动态组件</h1>
        <div>
            <button v-for="(item, index) in tableList" @click="change(index)">{{item.title}}</button>
        </div>

        <!--   通过keep-alive包裹的组件将会被缓存,且默认会缓存内部的所有组件实例    -->
        <keep-alive>
            <component :is="currentComponent.com"></component>
        </keep-alive>

        <!--   我们可以通过 include 和 exclude prop 来定制该行为。这两个 prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组    -->
        <!-- 以英文逗号分隔的字符串 -->
        <keep-alive include="a,c">
            <component :is="currentComponent.com"></component>
        </keep-alive>
        <!-- 正则表达式 (需使用 `v-bind`) -->
        <keep-alive :include="/a|c/">
            <component :is="currentComponent.com"></component>
        </keep-alive>

        <!-- 数组 (需使用 `v-bind`) -->
        <keep-alive :include="['a', 'c']">
            <component :is="currentComponent.com"></component>
        </keep-alive>
    </div>
</template>
<script setup>
import A from './components/A.vue'
import B from './components/B.vue'
import C from './components/C.vue'
import {reactive, markRaw} from "vue";

// 关于markRaw,标记一个对象,使其永远不会转换为 proxy。返回对象本身。意思就是使其不能成为一个响应式对象。
// 跳过 proxy 转换可以提高性能。
const tableList = reactive([
        {title: "A组件", com: markRaw(A)},
        {title: "B组件", com: markRaw(B)},
        {title: "C组件", com: markRaw(C)},
])
const currentComponent = reactive({
    com:tableList[0].com
})
function change(index) {
    currentComponent.com = tableList[index].com
}
</script>
<style scoped>
</style>