Vue 动态组件(Dynamic Components)

35 阅读3分钟

Vue 动态组件(Dynamic Components)

动态组件是 Vue 中一个非常实用的特性,它允许我们在同一个挂载点(一个 <component> 元素)上动态地切换不同的组件。这种机制使得组件的渲染逻辑更加灵活,尤其在需要根据用户交互或应用状态改变视图时非常有用。

什么是动态组件

简单来说,动态组件就是通过一个特殊的 <component> 元素,并绑定其 is 属性来决定当前要渲染的组件is 属性的值可以是一个已注册的组件名,也可以是一个导入的组件对象。

is 的值发生变化时,Vue 就会销毁旧的组件实例并用新的组件替换。

基本用法

使用 <component> 元素

在模板中,使用 <component> 标签,并通过 :is 绑定要渲染的组件:

<template>
  <component :is="currentComponent"></component>
</template>
​
<script setup>
import { ref } from 'vue'
import ComponentA from './ComponentA.vue'
import ComponentB from './ComponentB.vue'
​
const currentComponent = ref(ComponentA) // 也可以使用组件名 'ComponentA'
</script>

:is 的两种绑定方式

  • 绑定组件对象(推荐):直接导入组件并绑定。

    <script setup>
    import { ref } from 'vue'
    import ComponentA from './ComponentA.vue'
    import ComponentB from './ComponentB.vue'
    ​
    const currentComponent = ref(ComponentA) // 也可以使用组件名 'ComponentA'
    </script>
    
  • 绑定组件名称字符串:组件必须在 components 选项中注册(选项式API)或全局注册。

    <script>
    // 选项式 API 示例
    export default {
      data() {
        return {
          current: 'MyComponent'
        }
      },
      components: {
        MyComponent
      }
    }
    </script>
    

实际示例:通过按钮切换组件

<template>
  <div>
    <button
      v-for="tab in tabs"
      :key="tab"
      @click="currentTab = tab"
      :class="{ active: currentTab === tab }"
    >
      {{ tab }}
    </button>
​
    <component :is="currentTabComponent" class="tab-content"></component>
  </div>
</template>
​
<script setup>
import { ref, computed } from 'vue'
import HomeTab from './HomeTab.vue'
import PostsTab from './PostsTab.vue'
import ArchiveTab from './ArchiveTab.vue'
​
const tabs = ['Home', 'Posts', 'Archive']
const currentTab = ref('Home')
​
const currentTabComponent = computed(() => {
  switch (currentTab.value) {
    case 'Home': return HomeTab
    case 'Posts': return PostsTab
    case 'Archive': return ArchiveTab
    default: return HomeTab
  }
})
</script>

使用 <keep-alive> 缓存组件状态

默认情况下,每次切换动态组件,Vue 都会销毁旧组件并创建新组件,这意味着组件内部的状态会丢失。如果我们希望保留组件的状态(例如表单输入内容、滚动位置等),可以将 <component> 包裹在 <keep-alive> 标签内。

<template>
  <keep-alive>
    <component :is="currentTabComponent"></component>
  </keep-alive>
</template>

这样,被切换掉的组件会被缓存,而不是销毁。当再次切换回来时,组件会从缓存中恢复,保留之前的状态。

按条件缓存

<keep-alive> 还支持 includeexclude 属性,用于指定哪些组件需要被缓存(通过组件名称匹配)。

<keep-alive include="HomeTab,PostsTab">
  <component :is="currentTabComponent"></component>
</keep-alive>

动态组件与异步组件结合

当应用较大时,我们可以结合 Vue 的异步组件来按需加载,提高首屏加载速度。

<template>
  <component :is="asyncComponent"></component>
</template>
​
<script setup>
import { ref, defineAsyncComponent } from 'vue'
​
const asyncComponent = ref(null)
​
// 假设在某个时机加载组件
function loadComponent() {
  asyncComponent.value = defineAsyncComponent(() =>
    import('./HeavyComponent.vue')
  )
}
</script>

动态组件的 is 属性可以直接接收一个异步组件工厂函数,Vue 会在需要渲染时自动解析它。

注意事项

is 属性的绑定方式(Vue 2 vs Vue 3)

  • Vue 3:直接使用 :is="组件对象/名称",无需额外指令。
  • Vue 2:动态组件的 is 属性通常写作 :is="componentName",但如果想直接传入组件对象,需要使用 is 属性并配合 v-bind

避免使用 HTML 元素名作为组件名

在 Vue 3 中,如果将 is 绑定到一个 HTML 标签名(如 'div'),Vue 会将其渲染为普通 HTML 元素,而不是 Vue 组件。这通常用于在原生元素上动态切换标签,但如果你想要的是 Vue 组件,请确保绑定的值是组件对象或已注册的组件名称。

XSS 防范

永远不要将用户可编辑的内容直接作为 is 的值(例如通过 v-html 或拼接字符串),否则可能导致 XSS 攻击。应当始终使用受控的组件名或组件对象。

v-if / v-else 的选择

  • 如果只有少数几个固定组件的切换,使用 v-if / v-else-if / v-else 也可以。
  • 当组件的数量不确定或需要动态变化时,动态组件更加简洁。