本文 AI 產出,尚未審核

Vue3 非同步與資料請求

主題:非同步元件(defineAsyncComponent


簡介

在單頁應用(SPA)中,隨著功能日益龐大,首屏載入的體驗往往會因為一次性載入過多的程式碼而受影響。Vue 3 為了解決這個問題,提供了 非同步元件(Async Component) 的機制,讓開發者可以把較不常使用或體積較大的元件,等到真正需要時才動態載入。

defineAsyncComponent 是 Vue 3 官方推薦的 API,它不僅支援基本的懶載入,還提供了 載入中、錯誤、超時 等回饋機制,讓使用者在等待過程中不會看到空白頁面,同時也能更好地處理網路錯誤。掌握這項技術,能顯著降低首屏 bundle 大小、提升應用效能,並且改善使用者體驗,是現代前端開發不可或缺的功力。


核心概念

1. 為什麼要使用非同步元件?

  • 減少首屏載入時間:只載入當前路由或當前視圖真正需要的元件,其他元件保留在伺服器端,待使用時再下載。
  • 提升效能:瀏覽器同時下載的資源有限,將較大的元件拆分成多個小檔,可讓瀏覽器更有效率地利用網路帶寬。
  • 改善使用者體驗:配合 loading、error UI,使用者不會因為等待而感到卡頓或不確定。

2. defineAsyncComponent 基本語法

Vue 3 內建的 defineAsyncComponent 接受一個返回 Promise 的函式,這個 Promise 必須 resolve 為一個元件定義(Component)。最簡單的寫法如下:

import { defineAsyncComponent } from 'vue'

const AsyncHello = defineAsyncComponent(() => import('./components/Hello.vue'))

在模板中使用時,和普通元件沒有差別:

<template>
  <AsyncHello />
</template>

3. 進階選項:loading、error、delay、timeout

defineAsyncComponent 也接受一個 物件,可以設定以下屬性:

屬性 型別 說明
loader () => Promise<Component> 必填,返回元件的函式
loadingComponent Component 載入期間顯示的元件
errorComponent Component 載入失敗時顯示的元件
delay number (ms) 在顯示 loadingComponent 前的延遲時間,預設 200ms
timeout number (ms) 載入逾時的時間,逾時會觸發 errorComponent,預設無限制
onError (error, retry, fail, attempts) => void 自訂錯誤處理,允許手動 retryfail

範例 1:最完整的非同步元件寫法

import { defineAsyncComponent } from 'vue'

// 1. 先建立 Loading、Error 兩個小元件
const LoadingSpinner = {
  template: `<div class="spinner">載入中…</div>`
}
const LoadError = {
  template: `<div class="error">載入失敗,請稍後再試。</div>`
}

// 2. 使用 defineAsyncComponent
export const AsyncChart = defineAsyncComponent({
  // 必填:載入目標元件
  loader: () => import('./components/Chart.vue'),

  // 載入期間顯示的 UI
  loadingComponent: LoadingSpinner,

  // 載入失敗時的 UI
  errorComponent: LoadError,

  // 只在超過 300ms 後才顯示 LoadingSpinner,避免閃爍
  delay: 300,

  // 10 秒仍未成功載入即視為逾時
  timeout: 10000,

  // 自訂錯誤處理:允許使用者重試
  onError(error, retry, fail, attempts) {
    if (attempts < 3) {
      // 最多重試三次
      console.warn(`載入失敗,第 ${attempts + 1} 次重試…`)
      retry()
    } else {
      fail()
    }
  }
})

Tiploader 必須回傳 ES Module(即 import()),因此不要使用 require 或 CommonJS。

4. 與 Vue Router 的結合

在路由層級直接使用非同步元件,可以讓整個路由分支在第一次切換時才下載相對應的程式碼。

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

const UserProfile = defineAsyncComponent(() => import('../views/UserProfile.vue'))
const Settings = defineAsyncComponent(() => import('../views/Settings.vue'))

const routes = [
  {
    path: '/user/:id',
    component: UserProfile
  },
  {
    path: '/settings',
    component: Settings
  }
]

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

5. 動態載入多個元件(組件庫)

如果你有一個大型的 UI 套件(例如 ElementPlusAnt Design Vue),可以把每個子元件做成非同步的,減少一次性載入的重量。

// utils/asyncUI.js
import { defineAsyncComponent } from 'vue'

export const AsyncButton = defineAsyncComponent(() =>
  import('element-plus/lib/components/button')
)
export const AsyncDialog = defineAsyncComponent(() =>
  import('element-plus/lib/components/dialog')
)

在需要的地方直接引用:

<template>
  <AsyncButton type="primary" @click="open">開啟對話框</AsyncButton>
  <AsyncDialog v-model="showDialog" title="非同步對話框">
    <p>這是一個使用 async component 的 Dialog。</p>
  </AsyncDialog>
</template>

<script setup>
import { ref } from 'vue'
import { AsyncButton, AsyncDialog } from '@/utils/asyncUI'

const showDialog = ref(false)
const open = () => (showDialog.value = true)
</script>

常見陷阱與最佳實踐

陷阱 原因 解決方式
未設定 delay,導致短暫的 loading UI 閃爍 loadingComponent 立即顯示,會在 10~30ms 內出現閃爍 設定 delay: 200(或根據實際情況調整)
忘記 timeout,在網路不佳時永遠卡住 loading loader 永遠不 resolve,使用者無法得知失敗原因 加上 timeout: 8000(8 秒)或自訂 onError
loader 回傳非 ES Module,導致錯誤 import() 必須回傳 default 匯出的元件 確認 export default 正確,或使用 () => import('./Comp.vue').then(m => m.default)
過度切分,導致過多 HTTP 請求 每個小元件都獨立請求,會產生過多連線 使用 webpackChunkNamevitemanualChunks 合理分組
在 SSR 中使用,因為 SSR 需要同步的元件 非同步載入在伺服器端無法即時取得 在 SSR 環境下使用 defineAsyncComponent 時,確保 loader 能在伺服器端同步執行或使用 ssr: false 的方式跳過

最佳實踐

  1. 適度切分:只針對「首次載入不必要」或「體積較大」的元件使用 async。
  2. 提供 Loading UI:即使是 200ms 延遲,也建議提供簡潔的 spinner,提升感知效能。
  3. 錯誤重試機制:使用 onError 讓使用者有機會手動重試,尤其在行動裝置或不穩定的網路環境。
  4. 命名分塊:在 Vite 中,可使用 /* webpackChunkName: "user-profile" */ 註解來自訂 chunk 名稱,方便除錯與 CDN 緩存。
  5. 配合 Prefetch/Preload:對於即將進入的路由,可在 <router-link> 上加上 prefetch,提前下載下一個 async component。
<router-link to="/settings" v-slot="{ navigate, href }">
  <a :href="href" @click="navigate" rel="prefetch">設定</a>
</router-link>

實際應用場景

場景 為何適合使用 defineAsyncComponent
大型儀表板:包含多個圖表、地圖、報表 圖表套件(如 echarts)體積龐大,僅在使用者切換到相關分頁時才載入
電商商品詳情頁:每個商品可能需要不同的評論、推薦模型 評論區塊、即時推薦列表可分別非同步載入,提升主圖與基本資訊的渲染速度
管理後台的設定頁:功能眾多但使用頻率低 如「權限管理」或「系統日誌」等頁面,可延遲載入,減少首頁載入時間
行動端 APP:網路環境多變 透過 timeout + retry 機制,確保在慢速網路下仍能提供回饋
多語系或主題切換:不同語系/主題可能需要不同的 UI 元件 只在使用者切換語系或主題時才載入對應的字體或樣式組件

範例:電商商品頁的非同步評論區

// components/ProductReview.vue
import { defineAsyncComponent } from 'vue'

export const AsyncReview = defineAsyncComponent({
  loader: () => import('./ReviewList.vue'),
  loadingComponent: {
    template: `<div class="review-loading">載入評論中…</div>`
  },
  errorComponent: {
    template: `<div class="review-error">評論載入失敗,<button @click="$emit('retry')">重試</button></div>`
  },
  delay: 200,
  timeout: 8000,
  onError(error, retry, fail) {
    // 若是 404(評論功能尚未開啟),直接顯示空狀態
    if (error.message.includes('404')) fail()
    else retry()
  }
})
<template>
  <section class="product-detail">
    <h1>{{ product.name }}</h1>
    <p>{{ product.description }}</p>

    <!-- 只在需要時載入評論 -->
    <AsyncReview @retry="reloadReview" />
  </section>
</template>

<script setup>
import { ref } from 'vue'
import { AsyncReview } from '@/components/ProductReview'

const product = ref({
  name: 'Vue 3 超級筆記本',
  description: '結合最先進的前端技術…'
})

function reloadReview() {
  // 重新渲染 AsyncReview,會觸發 loader 再次執行
  // 這裡只需要重新掛載組件即可
}
</script>

總結

  • defineAsyncComponent 是 Vue 3 官方提供的懶載入 API,能讓我們把不立即需要的元件拆分成 非同步,減少首屏 bundle 大小,提升效能與使用者體驗。
  • 透過 loadingComponent、errorComponent、delay、timeout、onError 等選項,我們可以在載入過程中提供即時回饋,並在錯誤時給予重試機制。
  • Vue RouterUI 套件SSR 等環境結合時,需要注意 分塊策略錯誤處理 以及 預載(prefetch) 的時機。
  • 常見陷阱包括忘記設定 delaytimeout、或是 loader 回傳非 ES Module,透過最佳實踐(適度切分、提供 UI、命名分塊、配合 prefetch)即可避免。

掌握非同步元件的使用,能讓你的 Vue 3 應用在 效能可維護性 以及 使用者體驗 上都更上一層樓。現在就把這些技巧應用到實際專案中,感受載入速度的明顯提升吧!祝開發愉快 🎉