如果有任何問題或建議,歡迎隨時聯繫我:
昨天,我們優化了應用的「執行期 (Runtime)」效能,讓它在載入後能流暢地運作。但還有一個同樣重要的戰場:「載入期 (Load-time)」效能。也就是說,使用者在輸入網址、按下 Enter 後,需要等待多久才能看到有意義的內容?
預設情況下,像 Vite 或 Webpack 這類的打包工具 (Bundler),會將你專案中所有的 JavaScript 程式碼,打包成一個巨大的 bundle.js 檔案。隨著你的應用功能越來越多、頁面越來越複雜,這個檔案的體積也會像滾雪球一樣越變越大。
這會導致一個災難性的後果:使用者必須先下載完這整個幾 MB 大小的檔案,你的 Vue 應用才能開始渲染。這中間的漫長等待,就是我們常說的「白畫面時間 (White Screen of Death)」,也是使用者流失的主要原因之一。
打個比方:這就像搬家時,你把所有家當——床、沙發、書、廚具、衣服——全都塞進一個巨大無比的箱子裡。在你的新家,你必須等這個超級大箱子運到之後,才能開始拿出東西、佈置房間。在那之前,你只能待在空無一物的房子裡乾等。
這顯然不合理,對吧?一個更聰明的做法是,把家當分門別類地裝進不同的小箱子(「廚房」、「臥室」、「急救箱」)。先把最重要的「急救箱」送達,你至少能開始基本的生活。其他的箱子,可以等你需要時再陸續送到。
這個「分箱打包」的策略,在前端世界裡就叫做 Code Splitting (程式碼分割),而「需要時再送到」的戰術,就叫做 Lazy Loading (延遲載入)。
這是最常用,也是最有效的程式碼分割方式。
核心思想:使用者在訪問 A 頁面時,根本不需要 B、C、D 頁面的程式碼。我們應該只載入當前路由對應的程式碼。當使用者導航到新路由時,再即時去下載該路由的程式碼。
比喻:這就像你看 Netflix。你不會在點開一部電影時,就把整個 Netflix 影片庫都下載到你的電腦上。你只會「串流 (stream)」你正在看的那一部。
如何實現:在 vue-router 的設定中,將靜態的 import,改為動態的 import() 函式。
修改前 (所有程式碼打包在一起):
// src/router/index.js
import HomeView from '../views/HomeView.vue';
import AboutView from '../views/AboutView.vue';
import PostView from '../views/PostView.vue';
const routes = [
  { path: '/', name: 'home', component: HomeView },
  { path: '/about', name: 'about', component: AboutView },
  { path: '/post', name: 'post', component: PostView },
];
修改後 (Lazy Loading):
// src/router/index.js
const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('../views/HomeView.vue') // 動態 import
  },
  {
    path: '/about',
    name: 'about',
    // Webpack/Vite 看到這個語法,就會自動將 AboutView.vue 分割成一個獨立的 chunk 檔案
    component: () => import('../views/AboutView.vue')
  },
  {
    path: '/post',
    name: 'post',
    component: () => import('../views/PostView.vue')
  }
];
就這麼簡單!當打包工具看到 import() 這個函式語法時,它就會自動將這個模組及其依賴,打包成一個獨立的 JavaScript 檔案 (chunk)。這個檔案只會在使用者第一次訪問該路由時,才被非同步地從伺服器下載。
有時候,程式碼分割的粒度需要更細。在同一個頁面中,可能也存在一些「大而重」,但並非立即需要的元件。
常見場景:
解決方案:使用 Vue 提供的 defineAsyncComponent 函式。
比喻:這就像一個新聞網站,文章的標題和內文會立刻載入,但頁面下方的互動式地圖或影片播放器,可以等到使用者滾動到那個位置時,再開始載入。
如何實現:用 defineAsyncComponent 包覆一個動態 import()。
<template>
  <button @click="show = true">打開重量級 Modal</button>
  <!-- HeavyModal 的程式碼,只會在 show 變為 true 的那一刻才開始下載 -->
  <HeavyModal v-if="show" @close="show = false" />
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue';
const show = ref(false);
// 使用 defineAsyncComponent 來延遲載入元件
const HeavyModal = defineAsyncComponent(() =>
  import('@/components/HeavyModal.vue')
);
</script>
defineAsyncComponent 還可以接受一個物件,讓我們可以定義載入時的 loadingComponent 和載入失敗時的 errorComponent,提供更完善的使用者體驗。
設定完 Lazy Loading 後,你該如何確認它真的生效了?
npm run build,觀察 dist/assets 資料夾。你會發現,除了主要的 index-xxxx.js 檔案外,還多出了很多以數字或元件名命名的 js 小檔案。這些就是被分割出來的 chunks。js 檔案體積變小了。js 檔案被即時下載。vue-router 的所有路由,全部改寫成 Lazy Loading 的形式。打開 Network 頁籤,比較一下修改前後,初始載入的檔案大小和數量有何變化。v-if 後面的元件),並使用 defineAsyncComponent 對它進行改造。今天,我們學會了如何解決前端應用「過於肥胖」的問題,為使用者提供更快的首屏體驗。
vue-router 中使用 () => import(...) 實現。defineAsyncComponent 處理頁面中非必要的重量級元件。掌握 Code Splitting,是所有現代前端工程師的必備技能。它能從根本上改善你的應用載入效能,給使用者留下最好的第一印象。
明天,我們將探討一個軟體工程中的永恆話題:專案重構與可維護性。
載入期效能 (Load-time Performance)
程式碼分割 (Code Splitting)
延遲載入 (Lazy Loading)
打包工具 (Bundler)
Chunk
動態 Import: import()
defineAsyncComponent
首屏時間 (First Paint)