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。 - 可以透過
loadingComponent、errorComponent、delay、timeout等選項提供載入中與錯誤的 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 提前下載
當我們預測使用者很可能在短時間內會進入某個頁面時,可以利用瀏覽器的 prefetch 或 preload 讓資源在空閒時間提前下載。
// 在路由設定中使用 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
})
技巧:
delay與timeout能有效提升使用者體驗,避免瞬間出現的 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() 可能在伺服器端產生不必要的請求。 |
使用 defineAsyncComponent 的 ssr: false 或在 if (import.meta.env.SSR) 判斷。 |
最佳實踐摘要
- 分層懶載入:先在路由層面做 code‑splitting,再針對大型元件或第三方庫使用
defineAsyncComponent。 - 適時使用 Prefetch:在使用者可能點擊的連結上加入
prefetch,提升感知速度。 - 提供 Loading / Error UI:使用
loadingComponent、errorComponent,讓使用者在等待時不會感到空白。 - 統一錯誤處理:封裝
safeImport或全局的errorHandler,避免個別忘記捕獲例外。 - 監控 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 以及 錯誤處理,我們可以顯著降低首屏載入時間、減少不必要的網路流量,進而提升使用者滿意度。掌握以下幾點,即可在實務專案中安全、有效地運用動態匯入:
- 先規劃懶載入層級:路由 → 大型元件 → 第三方庫。
- 提供 Loading / Error UI,避免空白或卡死。
- 適度使用 Prefetch,但避免過度導致帶寬浪費。
- 統一錯誤捕獲,如
safeImport或全局處理。 - 持續監控 Bundle 大小,確保 chunk 分割合理。
只要遵循這些原則,你的 Vue3 應用就能在保持功能完整的同時,呈現更快、更流暢的使用者體驗。祝開發順利,Happy Coding! 🚀