本文 AI 產出,尚未審核

Vue3 元件基礎 – 動態元件(component :is

簡介

在單頁應用 (SPA) 中,元件是組成 UI 的基本單位。隨著需求變化,我們常需要根據使用者操作、資料狀態或路由動態切換不同的元件,而不想每次都重新掛載整個頁面。Vue3 提供的 <component :is="..."> 讓這件事變得既簡潔又高效。

動態元件不僅可以減少重複的模板程式碼,還能延遲載入(lazy‑load)或條件渲染,提升首屏渲染速度與使用者體驗。本文將從概念說明、實作範例、常見陷阱到最佳實踐,完整呈現 :is 的使用方式,幫助初學者快速上手,同時提供中級開發者可直接套用的技巧。


核心概念

1. :is 是什麼?

<component> 是 Vue 內建的抽象元件,is 屬性接受 字串、元件物件或函式,決定要渲染哪一個具體元件。語法如下:

<component :is="componentNameOrObject"></component>
  • 字串:指向已註冊的全域或局部元件名稱。
  • 元件物件:直接傳入 import 後的元件。
  • 函式:返回上述任意類型,可用於條件判斷或計算屬性。

2. 為什麼要用 <component :is>

情境 傳統寫法 使用 :is 的好處
多個相似區塊切換 多個 <template v-if> 單一入口,減少模板冗長
動態載入大型元件 直接 import 整個檔案 搭配 defineAsyncComponent,支援懶載入
表單欄位根據類型變化 多個 <input> + v-show 可插拔的元件化設計,維護成本下降

3. 基本使用範例

範例 1:字串切換全域元件

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import Foo from './components/Foo.vue'
import Bar from './components/Bar.vue'

const app = createApp(App)
app.component('Foo', Foo)   // 全域註冊
app.component('Bar', Bar)
app.mount('#app')
<!-- App.vue -->
<template>
  <div>
    <button @click="current = 'Foo'">顯示 Foo</button>
    <button @click="current = 'Bar'">顯示 Bar</button>

    <!-- 動態元件 -->
    <component :is="current"></component>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const current = ref('Foo')   // 初始顯示 Foo
</script>

說明current 為字串,會自動對應全域註冊的 FooBar 元件,點擊按鈕即切換。

範例 2:直接傳入元件物件(局部註冊)

<!-- Parent.vue -->
<template>
  <button @click="show = !show">切換</button>

  <component :is="show ? ChildA : ChildB"></component>
</template>

<script setup>
import ChildA from './ChildA.vue'
import ChildB from './ChildB.vue'
import { ref } from 'vue'

const show = ref(true)
</script>

說明show 為布林值,三元運算子直接回傳 元件物件,不需要事先註冊名稱。

範例 3:配合 defineAsyncComponent 實現懶載入

// DynamicChart.vue
import { defineAsyncComponent, ref } from 'vue'

const ChartA = defineAsyncComponent(() =>
  import('./charts/ChartA.vue')
)
const ChartB = defineAsyncComponent(() =>
  import('./charts/ChartB.vue')
)

export default {
  setup() {
    const type = ref('A')
    const currentChart = computed(() => (type.value === 'A' ? ChartA : ChartB))
    return { type, currentChart }
  }
}
<!-- DynamicChart.vue template -->
<template>
  <select v-model="type">
    <option value="A">圖表 A</option>
    <option value="B">圖表 B</option>
  </select>

  <component :is="currentChart"></component>
</template>

說明:只有在使用者真的切換到某個圖表時,對應的檔案才會被下載,降低首次載入體積。

範例 4:使用函式返回元件(高階組件)

// HighOrder.vue
import { h, resolveComponent } from 'vue'

export default {
  props: {
    kind: { type: String, required: true }
  },
  render() {
    const componentName = this.kind === 'edit' ? 'EditForm' : 'ViewDetail'
    // resolveComponent 會自動搜尋全域或局部註冊的元件
    const Comp = resolveComponent(componentName)
    return h(Comp, { /* 傳入需要的 props */ })
  }
}

說明:在 render 函式 中使用 resolveComponent,可根據 kind 動態決定要渲染的元件,適合需要高度抽象的 UI。

範例 5:搭配 v-slot 共享插槽

<!-- TabContainer.vue -->
<template>
  <div class="tabs">
    <button v-for="t in tabs" :key="t" @click="active = t">{{ t }}</button>
  </div>

  <!-- 動態元件同時提供預設插槽 -->
  <component :is="components[active]">
    <template #default="slotProps">
      <slot :data="slotProps"></slot>
    </template>
  </component>
</template>

<script setup>
import { ref } from 'vue'
import TabA from './TabA.vue'
import TabB from './TabB.vue'

const tabs = ['A', 'B']
const components = { A: TabA, B: TabB }
const active = ref('A')
</script>

說明:外層容器只負責切換,內部元件仍保有自己的插槽結構,插槽資料 透過 v-slot 向上傳遞,實現高度可組合的 Tab 系統。


常見陷阱與最佳實踐

1. 忘記註冊元件

如果 :is 接收的是字串,卻未在全域或局部註冊對應的元件,Vue 會在控制台拋出 Unknown custom element 警告。解決方式:

  • 使用 局部註冊components: { Foo, Bar }
  • 或改為直接傳入 元件物件

2. 懶載入時未提供 loading / error 組件

defineAsyncComponent 允許設定 loadingComponenterrorComponentdelay 等參數,否則在網路慢的情況下使用者會看到空白。範例:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./HeavyComp.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: LoadError,
  delay: 200, // 200ms 後才顯示 loading
  timeout: 5000
})

3. 動態切換時保留舊元件狀態

預設情況下,切換 :is銷毀舊元件,重新建立新元件。如果希望保留狀態(例如表單輸入),可以使用 <keep-alive> 包裹:

<keep-alive>
  <component :is="current"></component>
</keep-alive>

4. 過度依賴 :is 造成可讀性下降

雖然 :is 很靈活,但過度在同一模板裡寫多層條件會讓程式碼難以追蹤。建議將條件邏輯搬到 computed 或 setup,只保留簡潔的 :is 表達式。

5. 插槽作用域錯誤

在動態元件內使用 v-slot 時,必須確保插槽名稱與提供的作用域匹配。若插槽名稱寫錯,外層無法取得資料,Vue 會顯示 Slot "default" received no data


實際應用場景

場景 為何選擇 :is 範例說明
表單類型切換(如「新增」 vs 「編輯」) 同一容器內切換不同欄位結構 透過 is="isEdit ? EditForm : CreateForm"
多語系 UI 根據使用者語系載入對應文字元件 :is="locale === 'zh' ? ZhComponent : EnComponent"
圖表儀表板 大量圖表只在需要時才載入 搭配 defineAsyncComponent 懶載入
Tab / Accordion 每個 Tab 內部可能是完全不同的元件 使用 components[activeTab] 作為 :is
插件化系統 允許第三方開發者提供自訂元件 透過 props.component 動態注入外部元件

實務提示:在大型專案中,建議建立一個 DynamicWrapper 組件,統一處理 :iskeep-aliveloading 等共通需求,減少每次手寫相同程式碼。


總結

  • <component :is="..."> 是 Vue3 提供的 抽象元件,讓開發者能根據資料、狀態或使用者操作動態切換渲染的元件。
  • 它支援 字串、元件物件與返回元件的函式,並可結合 defineAsyncComponentkeep-alive、插槽等特性,打造高效、可維護的 UI。
  • 常見陷阱包括未註冊元件、懶載入缺少 loading/error 組件、切換時狀態遺失等,透過 全域/局部註冊、loading 組件、keep-alive 可輕鬆避免。
  • 在實務開發中,動態元件特別適合 表單切換、圖表懶載入、Tab 系統、插件化 等需求,能顯著減少重複程式碼與提升使用者體驗。

掌握 :is 的使用方式,將讓你的 Vue3 專案在 彈性效能可維護性 上都有顯著提升。祝你開發順利,持續玩出更有趣的 Vue3 應用!