跳至主要內容

Vue 3 動態元件載入與非同步元件實戰

6 分鐘閱讀 1,050 字

Vue 3 動態元件載入與非同步元件實戰

在大型 Vue 3 應用中,把所有元件一次性打包進主 bundle 會讓初次載入時間大幅增加。動態元件載入非同步元件是解決這個問題的利器。本文將系統性地介紹這兩個功能,並示範實際的使用場景。

動態元件:``

Vue 的 <component> 特殊元素搭配 :is prop,可以在執行時決定要渲染哪個元件:

<script setup>
import { ref } from 'vue';
import TabHome from './TabHome.vue';
import TabProfile from './TabProfile.vue';
import TabSettings from './TabSettings.vue';

const tabs = {
  home: TabHome,
  profile: TabProfile,
  settings: TabSettings
};

const currentTab = ref('home');
</script>

<template>
  <div class="tabs">
    <button
      v-for="(_, name) in tabs"
      :key="name"
      @click="currentTab = name"
      :class="{ active: currentTab === name }"
    >
      {{ name }}
    </button>
    <component :is="tabs[currentTab]" />
  </div>
</template>

保留狀態:``

預設情況下,切換動態元件時,被隱藏的元件會被銷毀,狀態也會遺失。用 <KeepAlive> 包裹可以快取元件實例:

<template>
  <KeepAlive :include="['TabHome', 'TabProfile']" :max="3">
    <component :is="tabs[currentTab]" />
  </KeepAlive>
</template>
  • :include:只快取指定名稱的元件
  • :exclude:排除指定名稱的元件
  • :max:最多快取幾個元件實例

<KeepAlive> 快取的元件會觸發 onActivatedonDeactivated 生命週期鉤子:

import { onActivated, onDeactivated } from 'vue';

onActivated(() => {
  // 元件從快取中被重新啟用
  fetchLatestData();
});

onDeactivated(() => {
  // 元件被放入快取(不是銷毀)
  stopPolling();
});

非同步元件:`defineAsyncComponent`

defineAsyncComponent 讓你把元件包裝成懶載入的版本,只有在真正需要渲染時才發出網路請求:

import { defineAsyncComponent } from 'vue';

// 基本用法
const AsyncHeavyChart = defineAsyncComponent(
  () => import('./HeavyChart.vue')
);

// 進階設定
const AsyncEditor = defineAsyncComponent({
  loader: () => import('./RichTextEditor.vue'),
  loadingComponent: LoadingSpinner,    // 載入中顯示的元件
  errorComponent: ErrorDisplay,        // 載入失敗顯示的元件
  delay: 200,                          // 顯示 loading 前的延遲(ms)
  timeout: 10000,                      // 超過此時間視為失敗
  onError(error, retry, fail, attempts) {
    if (attempts <= 3) {
      retry();  // 重試最多 3 次
    } else {
      fail();
    }
  }
});

搭配 Suspense 使用

<Suspense> 是 Vue 3 的實驗性功能,可以統一處理多個非同步元件的載入狀態:

<template>
  <Suspense>
    <!-- 預設插槽:實際內容 -->
    <template #default>
      <AsyncDashboard />
    </template>

    <!-- fallback 插槽:載入中顯示 -->
    <template #fallback>
      <div class="loading-screen">
        <Spinner />
        <p>正在載入儀表板...</p>
      </div>
    </template>
  </Suspense>
</template>

基於路由的程式碼分割

在 Vue Router 中,每個路由對應的元件都應該懶載入:

import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: () => import('./views/Home.vue')
    },
    {
      path: '/dashboard',
      // 使用 Vite 的 chunk 命名
      component: () => import(/* @vite-ignore */ /* webpackChunkName: "dashboard" */ './views/Dashboard.vue')
    },
    {
      path: '/admin',
      // 預載入:使用者進入 /dashboard 後就開始下載
      component: () => import('./views/Admin.vue')
    }
  ]
});

條件式預載入

針對使用者很可能下一步會造訪的頁面,可以在閒置時預載入:

// 在使用者登入後,預載入儀表板
async function onLoginSuccess() {
  // 先跳轉
  router.push('/dashboard');

  // 利用 requestIdleCallback 預載入其他可能用到的模組
  requestIdleCallback(() => {
    import('./views/Reports.vue');
    import('./views/Settings.vue');
  });
}

動態載入圖示庫

圖示庫往往是 bundle 大小的殺手。結合動態元件可以實現按需載入:

<script setup>
import { defineAsyncComponent, computed } from 'vue';

const props = defineProps({ name: String });

const IconComponent = computed(() =>
  defineAsyncComponent(() =>
    import(`../icons/${props.name}.vue`)
  )
);
</script>

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

效能分析

使用 Vite 的 bundle 分析工具來確認分割效果:

npm install --save-dev rollup-plugin-visualizer
// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer';

export default {
  plugins: [
    visualizer({
      open: true,
      gzipSize: true,
      filename: 'dist/stats.html'
    })
  ]
};

執行 npm run build 後會自動開啟視覺化報告,讓你清楚看到每個 chunk 的大小。

最佳實踐整理

  1. 路由層級必做懶載入:每個路由頁面都應使用動態 import
  2. 重型元件優先:圖表庫、富文字編輯器、PDF 渲染器等優先設為非同步
  3. 合理使用 KeepAlive:頻繁切換且有複雜狀態的元件才值得快取
  4. 設定 timeout 和錯誤元件:生產環境中非同步元件必須有降級方案
  5. 避免過度分割:太細的 chunk 反而會增加 HTTP 請求數,得不償失

小結

動態元件和非同步元件是 Vue 3 效能優化的重要工具。合理運用程式碼分割,可以將首屏載入時間減少 40-60%。關鍵是找到適當的分割粒度:路由層級一定要分,元件層級則視大小和使用頻率決定。

分享這篇文章