返回 导航

Vue.js

hangge.com

Vue.js - 动态组件使用详解(附:配合keep-alive保留组件状态)

作者:hangge | 2026-04-03 08:46
    在实际前端开发中,我们经常需要根据用户操作或运行时状态来灵活切换界面内容。例如在标签页(Tabs)切换、表单生成器、配置化界面、插件扩展点等场景中,不同功能块往往对应不同组件。如果只是简单地控制显示/隐藏,很多开发者第一反应是使用 v-ifv-show,但这两种方式都会把组件“写死”在模板中:v-if 每次切换会销毁并重建组件实例,而 v-show 虽然不会销毁,但只能控制同一个组件的显示状态,很难在多个组件之间动态切换。
    为了让组件能够像数据一样动态切换,Vue 提供了强大的 动态组件(Dynamic Components) 功能,通过 <component :is="..."> 可以让组件的类型由数据控制,从而真正实现“按需渲染”。搭配 keep-alive,我们甚至可以在组件之间来回切换而不丢失状态;配合异步组件,还能实现按需加载,减少首屏体积。下面我将通过样例演示动态组件的使用。

1,基本用法

(1)假设我们需要实现一个通过单击按钮实现切换显示不同的组件(HomeAbout)的功能。
  • 其中 Home.vue 组件代码如下:
<template>
    <div class="layout">
        Home组件
    </div>
</template>
<script>
export default {
    name: 'home'
}
</script>
<style scoped>
.layout {
    border: 1px solid #ccc;
    padding: 20px;
    margin: 20px;
}
</style>
<template>
    <div class="layout">
        About组件
    </div>
</template>
<script>
export default {
    name: 'about'
}
</script>
<style scoped>
.layout {
    border: 1px solid #ccc;
    padding: 20px;
    margin: 20px;
}
</style>

(2)接着我们使用 Vue.js 内置的 <component> 组件,并通过 is 属性指定动态显示的组件。当 currentTabhome 字符串时,显示 <home> 组件。这里的 is 属性用于指定组件的名称, 它支持指定全局或局部组件。
<template>
    <div class="app">
        <button v-for="item in tabs" @click="itemClick(item)"
             :class="{ active: currentTab == item }">
            {{ item }}
        </button>
        <!-- 动态组件的实现(is属性是动态绑定组件的名称。例如is="home”,代表绑定<home/> 组件)-->
        <component :is="currentTab"></component>
    </div>
</template>
<script>
import Home from './Home.vue';
import About from './About.vue';

export default {
    components: {
        Home,
        About
    },
    data() {
        return {
            tabs: ["home", "about"],
            currentTab: "home"
        }
    },
    methods: {
        itemClick(item) {
            this.currentTab = item;
        }
    }
}
</script>

(3)运行效果如下,默认显示 Home 组件,当单击不同的按钮,会切换显示不同的组件。

2,动态组件的传参

(1)在上述案例中,HomeAbout 组件都使用 <component> 动态组件进行替代。如果需要向这些组件传递 props 参数或者监听事件,可以直接向动态组件传递这些参数和事件, 这样就会传递到相应的组件中。我们只需要将属性和监听事件放到 <component> 组件中即可。
(2)这里我们修改父组件代码,从而向 HomeAbout 两个组件都传递 nameage 属性和监听 pageClick 事件。
<template>
    <div class="app">
        <button v-for="item in tabs" @click="itemClick(item)" 
            :class="{ active: currentTab == item }">
            {{ item }}
        </button>
        <!-- 动态组件的实现(is属性是动态绑定组件的名称。例如is="home”,代表绑定<home/> 组件)-->
        <component :is="currentTab" name="hangge" :age="18" @pageClick="pageClick">
        </component>
    </div>
</template>
<script>
import Home from './Home.vue';
import About from './About.vue';

export default {
    components: {
        Home,
        About
    },
    data() {
        return {
            tabs: ["home", "about"],
            currentTab: "home"
        }
    },
    methods: {
        itemClick(item) {
            this.currentTab = item;
        },
        // 监听动态组件的pageClick事件
        pageClick(value) {
            console.log(value);
        }
    }
}
</script>

(3)接着修改 Home.vue 组件。为 Home.vue 组件添加 props 属性,用于接收 nameage 属性,同时将两个属性显示在 template 中。接着当单击 <div> 元素时,会向父组件触发 pageClick 事件,并传递一个字符串参数。 
提示About.vue 组件也可以接收 nameage 属性和触发 pageClick 事件,这里就不给出代码了。
<template>
  <div @click="divClick">
    Home组件: {{name}} - {{age}}
  </div>
</template>

<script>
  export default {
    name: "home", 
    props: {
      name: { // 动态组件传递过来的 name
        type: String,
        default: ""
      },
      age: {
        type: Number,
        default: 0
      }
    },
    emits: ["pageClick"], // 动态组件监听的pageClick事件
    methods: {
      divClick() {
        this.$emit("pageClick", 'Home组件触发的点击');
      }
    }
  }
</script>

(4)运行结果如下,可以看到 Home.vue 组件可以正常接收 props,并且其触发的 pageClick 事件也可以被父组件监听到。

附:使用 keep-alive 缓存组件状态

1,keep-alive 的属性介绍

(1)<kep-alive> 组件的属性如下:
  • include:支持 stringRegExpArray 类型。只有名称匹配的组件会被缓存。 
  • exclude:支持 stringRegExpArray 类型。任何名称匹配的组件都不会被缓存。 
  • max:支持 numberstring 类型。最多可以缓存多少个组件实例,一旦达到这个数字,那么缓存组件中最近没有被访问的实例会被销毁。 
(2)其中,includeexclude 属性允许组件有条件地缓存,二者都可以表示为用逗号分隔的字符串、正则表达式或一个数组。匹配时会首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册的名称。 

2,使用方法

(1)默认 <kep-alive> 会缓存其内部的所有组件,假设我们我们仅想缓存 Home.vueAbout.vue 组件的状态,那么下面三种写法:
<!-- 1.逗号分隔字符串-->
<keep-alive include="home,about">
    <component :is="currentTab" name="hangge" :age="18" @pageClick="pageClick">
    </component>
</keep-alive>
<!-- 2.RegExp正则表达式-->
<keep-alive :include="/home|about/">
    <component :is="currentTab" name="hangge" :age="18" @pageClick="pageClick">
    </component>
</keep-alive>
<!-- 3.数组形式-->
<keep-alive :include="['home', 'about']">
    <component :is="currentTab" name="hangge" :age="18" @pageClick="pageClick">
    </component>
</keep-alive>

(2)这里我们对 Home.vue 组件进行改造,增加一个 <button> 按钮,单击按钮可以实现数字递增的功能。
<template>
    <div class="layout">
        Home组件:
        <button @click="counter++">点击递增:{{ counter }}</button>
    </div>
</template>
<script>
export default {
    name: 'home',
    data() {
        return {
            counter: 0
        }
    }
}
</script>

(3)测试一下,我们在 Home.vue 组件下点击按钮将将 counter 递增到 5。然后切换到 About.vue 组件,再切换回 Home.vue 组件时,可以发现计数到 5 的状态可以保留,并没有销毁。

3,缓存组件实例的生命周期

(1)如果一个组件被 keep-alive 包裹,那么它:
  • 首次进入时执行 mounted
  • 离开时不会销毁,不会执行 destroyed
  • 再次进入时会触发 activated
  • 离开时触发 deactivated
提示:对缓存组件来说,再次进入时不会再次执行 createdmounted 等生命周期函数。但在有些情况下,我们需要监听组件重新进入和离开的时机,这时可以使用 activateddeactivated 这两个生命周期钩子函数。 

(2)这里我们修改 About.vue 组件,添加 activateddeactivated 这两个生命周期函数,代码如下:
<template>
    <div class="layout">
        About组件
    </div>
</template>
<script>
export default {
    name: "about",
    created() {
        console.log("about created");
    },
    unmounted() {
        console.log("about unmounted");
    },
    // 下面两个是 缓存组件的生命周期  
    activated() {
        console.log("about activated");
    },
    deactivated() {
        console.log("about deactivated");
    }
}
</script>

(3)保存代码,在浏览器中依次单击“about”→“home”→“about”按钮可以看到控制台输出内容如下:
  • 当单击“about”按钮时,还没有创建 About.vue 组件,这时会先调用 created 函数,然后调用 activated 函数;
  • 当单击“home”按钮时,About.vue 组件的 deactivated 函数会回调,unmounted 函数并没有回调,因为组件并没有销毁;
  • 当再次单击“about”按钮时, About.vue 组件的 activated 函数又会回调。 
评论

全部评论(0)

回到顶部