本文 AI 產出,尚未審核

Vue3 教學 – 效能與最佳化:Dynamic Import 動態匯入


簡介

在單頁應用(SPA)中,隨著功能不斷擴充,最終會形成一個龐大的 JavaScript 包(bundle)。如果一次性把所有程式碼都載入瀏覽器,使用者必須等候較長的載入時間,且首屏渲染速度會受到嚴重影響。Dynamic Import(動態匯入) 正是 Vue3 官方推薦的效能優化手段之一,它讓我們可以在需要時才載入模組,達到「按需載入」與「程式碼分割」的效果。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶領讀者了解如何在 Vue3 專案中善用 import(),提升載入速度與使用者體驗。


核心概念

1. 為什麼要使用 Dynamic Import?

  • 減少初始載入大小:只載入當前畫面所需的程式碼,其餘部分在使用者觸發相應操作時才下載。
  • 提升首屏渲染速度:瀏覽器能更快解析與執行核心程式碼,讓 UI 更快呈現。
  • 改善使用者體驗:結合瀏覽器的 prefetch / preload 機制,可在空閒時間預先下載即將需要的模組。

在 Vue3 中,Dynamic Import 主要透過原生的 ES2020 import() 語法結合 Vue 的 defineAsyncComponent 或 Vue Router 的懶載入功能實現。

2. 基本語法

// 基本的動態匯入語法
import('path/to/module')
  .then(module => {
    // 使用匯入的模組
    module.default()
  })
  .catch(err => {
    console.error('載入失敗', err)
  })
  • import() 會回傳 Promise,成功時得到模組物件(module.default 為預設匯出)。
  • 這個語法在編譯階段會被 Webpack / Vite 轉換成 code‑splitting 的 chunk。

3. Vue3 中的 Async Component(非同步元件)

Vue3 提供 defineAsyncComponent 讓我們可以把元件以動態方式載入。

import { defineAsyncComponent } from 'vue'

const AsyncHello = defineAsyncComponent(() =>
  import('./components/HelloWorld.vue')
)
  • AsyncHello 被第一次渲染時,Vue 會自動觸發 import(),載入 HelloWorld.vue
  • 可以透過 loadingComponenterrorComponentdelaytimeout 等選項提供載入中與錯誤的 UI。

4. Vue Router 的路由層級 Code Splitting

在 Vue Router 中,我們常使用 路由懶載入 讓每個路由對應的頁面分割成獨立的 chunk。

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

const routes = [
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
  },
  {
    path: '/settings',
    name: 'Settings',
    component: () => import('@/views/Settings.vue')
  }
]

export default createRouter({
  history: createWebHistory(),
  routes
})
  • component: () => import('...') 為「懶載入」寫法,只有在使用者切換到該路由時才下載對應的檔案。
  • 註解 /* webpackChunkName: "about" */(或 Vite 的 /* @vite-ignore */)可自訂 chunk 名稱,方便分析與快取。

5. 結合 Prefetch / Preload 提前下載

當我們預測使用者很可能在短時間內會進入某個頁面時,可以利用瀏覽器的 prefetchpreload 讓資源在空閒時間提前下載。

// 在路由設定中使用 webpack 的 magic comment
{
  path: '/profile',
  component: () => import(
    /* webpackPrefetch: true, webpackChunkName: "profile" */
    '@/views/Profile.vue'
  )
}
  • webpackPrefetch: true 會在瀏覽器空閒時自動下載該 chunk,且不會阻塞當前的渲染。
  • 若想在使用者即將點擊連結前立即下載,可改用 webpackPreload: true,但會比 prefetch 更積極,需慎重使用。

程式碼範例

以下提供 5 個實用範例,說明在不同情境下如何使用 Dynamic Import。

範例 1️⃣:元件內部的條件動態匯入

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

const showChart = ref(false)

async function loadChart() {
  // 只在需要時才載入 Chart.js
  const { default: Chart } = await import('chart.js')
  // 初始化圖表...
  new Chart(document.getElementById('myChart'), { /* config */ })
  showChart.value = true
}
</script>

<template>
  <button @click="loadChart">顯示圖表</button>
  <canvas v-if="showChart" id="myChart"></canvas>
</template>

重點:使用 await import() 可讓程式碼更簡潔,同時保證只在使用者點擊時才下載第三方套件。


範例 2️⃣:定義帶 Loading UI 的 Async Component

// components/AsyncUserProfile.vue
import { defineAsyncComponent } from 'vue'
import LoadingSpinner from '@/components/LoadingSpinner.vue'
import LoadError from '@/components/LoadError.vue'

export default defineAsyncComponent({
  // 匯入實際的元件
  loader: () => import('./UserProfile.vue'),

  // 載入中顯示的元件
  loadingComponent: LoadingSpinner,

  // 錯誤時顯示的元件
  errorComponent: LoadError,

  // 延遲 200ms 後才顯示 loadingComponent,避免閃爍
  delay: 200,

  // 超過 10 秒仍未載入則視為失敗
  timeout: 10000
})

技巧delaytimeout 能有效提升使用者體驗,避免瞬間出現的 loading 畫面或無限等待。


範例 3️⃣:路由層級懶載入 + Prefetch

// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import(
      /* webpackChunkName: "dashboard", webpackPrefetch: true */
      '@/views/Dashboard.vue'
    )
  },
  {
    path: '/reports',
    name: 'Reports',
    component: () => import(
      /* webpackChunkName: "reports", webpackPrefetch: true */
      '@/views/Reports.vue'
    )
  }
]

export default createRouter({
  history: createWebHistory(),
  routes
})

說明webpackPrefetch: true 會在使用者瀏覽其他頁面時,預先下載 Dashboard 與 Reports 的 chunk,切換時感覺瞬間完成。


範例 4️⃣:使用 Vite 的 import.meta.glob 動態匯入多個元件

// 在某個父元件中動態載入全部的圖表元件
const modules = import.meta.glob('@/components/charts/*.vue')

// 假設使用者可以選擇圖表類型
async function loadChartComponent(name) {
  const path = `@/components/charts/${name}.vue`
  if (modules[path]) {
    const component = await modules[path]()
    // 動態註冊或直接渲染
    return component.default
  }
  throw new Error('找不到對應的圖表元件')
}

優點import.meta.glob 讓我們一次性取得符合條件的所有檔案路徑,配合 await 即可在需要時載入任意一個元件,適合插件化或多樣化 UI 場景。


範例 5️⃣:錯誤處理與重試機制

async function safeImport(modulePath, retries = 2) {
  try {
    const mod = await import(modulePath)
    return mod.default
  } catch (err) {
    if (retries > 0) {
      console.warn(`載入失敗,重試 ${retries} 次...`, err)
      return safeImport(modulePath, retries - 1)
    }
    // 最後仍失敗,拋出錯誤或回傳 fallback 元件
    console.error('最終載入失敗', err)
    return () => import('@/components/LoadError.vue')
  }
}

實務:在不穩定的網路環境下,加入重試機制可降低使用者因一次性失敗而看到錯誤畫面的機率。


常見陷阱與最佳實踐

陷阱 說明 解決方案
過度切割 把每個小功能都拆成獨立 chunk,會產生大量 HTTP 請求,反而拖慢載入速度。 只對大型、較少使用的模組或頁面做懶載入,保持合理的 chunk 大小(≈ 30–150KB)。
忘記加 default import() 回傳的模組物件需要 .default 才是預設匯出,直接使用會得到 undefined 使用 const { default: Comp } = await import('./MyComp.vue')module.default
預先載入過多 webpackPrefetch: true 若使用過度,會在空閒時下載過多檔案,浪費帶寬。 僅對預測使用率高檔案較大的 chunk 使用 prefetch。
錯誤未捕獲 動態匯入失敗(如 404、網路斷線)未處理,會導致整個應用卡死。 使用 .catch()try/catch 包住 import(),並提供 fallback UI。
SSR 與動態匯入衝突 在 Nuxt / Vite SSR 中直接使用 import() 可能在伺服器端產生不必要的請求。 使用 defineAsyncComponentssr: false 或在 if (import.meta.env.SSR) 判斷。

最佳實踐摘要

  1. 分層懶載入:先在路由層面做 code‑splitting,再針對大型元件或第三方庫使用 defineAsyncComponent
  2. 適時使用 Prefetch:在使用者可能點擊的連結上加入 prefetch,提升感知速度。
  3. 提供 Loading / Error UI:使用 loadingComponenterrorComponent,讓使用者在等待時不會感到空白。
  4. 統一錯誤處理:封裝 safeImport 或全局的 errorHandler,避免個別忘記捕獲例外。
  5. 監控 Chunk 大小:利用 Webpack Bundle Analyzer / Vite visualizer 觀測產生的 chunk,保持在合理範圍。

實際應用場景

場景 為何需要 Dynamic Import 實作要點
大型儀表板(Dashboard) 首屏只顯示概覽,詳細圖表與報表可延遲載入。 路由懶載入 Dashboard,圖表元件使用 defineAsyncComponent,圖表資料庫套件 chart.js 於點擊時才 import()
多語系或主題切換 每個語系/主題的字串或樣式檔案體積不小。 點擊切換語系時 import('@/i18n/zh-TW.js'),切換主題時 import('@/styles/theme-dark.css')(CSS 也可使用動態匯入)。
插件式功能(例如:即時聊天、通知) 大部分使用者不會同時使用所有插件。 在使用者點擊「開啟聊天」時 import('@/plugins/Chat.vue'),若失敗顯示備援 UI。
行動裝置與桌面版差異 手機端資源受限,需要盡量減少載入。 利用 navigator.userAgent 判斷平台,僅在桌面版 import('@/components/HeavyDesktop.vue')
離線/漸進式 Web App(PWA) 首次安裝時希望快速啟動,之後再下載其他功能。 使用 Service Worker 的 precache 配合 import(),在使用者離線時仍能提供已快取的 chunk。

總結

Dynamic Import 是 Vue3 效能最佳化 中最直接、最具威力的工具之一。透過 按需載入路由層級懶載入prefetch/preload 以及 錯誤處理,我們可以顯著降低首屏載入時間、減少不必要的網路流量,進而提升使用者滿意度。掌握以下幾點,即可在實務專案中安全、有效地運用動態匯入:

  1. 先規劃懶載入層級:路由 → 大型元件 → 第三方庫。
  2. 提供 Loading / Error UI,避免空白或卡死。
  3. 適度使用 Prefetch,但避免過度導致帶寬浪費。
  4. 統一錯誤捕獲,如 safeImport 或全局處理。
  5. 持續監控 Bundle 大小,確保 chunk 分割合理。

只要遵循這些原則,你的 Vue3 應用就能在保持功能完整的同時,呈現更快、更流暢的使用者體驗。祝開發順利,Happy Coding! 🚀