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> 快取的元件會觸發 onActivated 和 onDeactivated 生命週期鉤子:
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 的大小。
最佳實踐整理
- 路由層級必做懶載入:每個路由頁面都應使用動態 import
- 重型元件優先:圖表庫、富文字編輯器、PDF 渲染器等優先設為非同步
- 合理使用 KeepAlive:頻繁切換且有複雜狀態的元件才值得快取
- 設定 timeout 和錯誤元件:生產環境中非同步元件必須有降級方案
- 避免過度分割:太細的 chunk 反而會增加 HTTP 請求數,得不償失
小結
動態元件和非同步元件是 Vue 3 效能優化的重要工具。合理運用程式碼分割,可以將首屏載入時間減少 40-60%。關鍵是找到適當的分割粒度:路由層級一定要分,元件層級則視大小和使用頻率決定。
分享這篇文章