懶加載路由或元件的意思是當訪問該路由,或需要顯示該元件時,才載入該路由或元件。這做法會提升網頁效能,在打包時,如果某路由或元件設定了懶加載,就會獨立被拆成一個 JS 檔案,而非塞進 app.js 這個打包後的主要 JS 檔案裏,因此 app.js 主檔案的大小就會被縮小。當首次載入網站時,瀏覽器就花更少時間載入 app.js。而且會確保在載入完成當前頁面的資源後,在空餘時間才開始載入其他獨立拆出來的 JS 檔案。因此提高了網站的效能。
以下會再作詳細解說。
打包 Vue 專案後,會產生一些 JS 檔案。如果是大型應用程式或網站,可能會有一百個以上的路由。如果網絡速度稍慢,載入頁面的時間可能不止幾秒鐘了,最後使用者通常都會直接關掉。
但是,如果使用懶加載的話,當使用者進入此路由時,瀏覽器才載入這個路由。而不是一載入網站時,就把所有路由全都載入。所以,首次訪問網站時的等候時間就能被縮短。
由最基本的開始說起,當你建立一個附有 Vue router 功能的 Vue CLI 的專案後,你會發現預設有以下程式碼:
router/ index.js
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
]
預設 /about
路由是採用懶加載的手法。現在示範自行新增一個 '/products'
的路由:
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/products',
name: 'Products',
component: Products
}
]
打開 Network 檢查:
但既然 About 是懶加載,為何現在一打開首頁,就馬上載入 about.js
?打開 about.js
,會發現瀏覽器使用 prefetch 來載入 About。
Vue 官方文件有說明 prefetch 的意思:
<link rel="prefetch">
是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。
所以,Vue 的確有實現懶加載,做法就是以上提到,等當前頁面的檔案都載入完成後,才會在背後開始載入那些之後有需要用到的檔案。
你也可以打開 page source 檢查原始碼,看到 Vue 目前以 prefetch 方法載入 about.js
:
接著,現在嘗試把 Products 轉用懶加載。看看與以上的檔案大小差別。
{
path: '/products',
name: 'Products',
// webpackChunkName 是產生獨立 JS 檔時的檔名
component: () => import(/* webpackChunkName: "products" */ '../views/Products.vue')
}
結果:
webpackChunkName
註解寫上 products。有時候,我們想要把同類的路由,在打包時可以放在同一個 JS 檔案裏。我們只需要把 webpackChunkName 改為同一個即可。引用官方例子:
const UserDetails = () =>
import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
const UserDashboard = () =>
import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')
const UserProfileEdit = () =>
import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')
此功能背後是 Webpack 本身的 Code Splitting 功能,即是把程式碼分開一個個輸出,也就是 經常提到 chunk / bundle 的意思。作用是讓我們取得較小的檔案,並更易控制優先載入哪個檔案。把此概念應用到懶加載路由,就是把每個頁面都分成一個 chunk 輸出。
以上所有測試都是在首頁 http://localhost:8080/#/
進行。當訪問 About 頁時,結果會怎樣?
結果:
如果點擊 All tab 來看,會發現有兩個 about.js,第一個是 prefetch 載入,第二個只是 prefetch cache 而己:
點擊 JS tab 會更清楚。當訪問 /about
頁時,其實只是載入之前已在首頁 prefetch 取得的 about.js 而己:
總結來說,以上測試得知,使用懶加載路由可以縮減第一次載入網站時的等待時間。使用懶加載把頁面分拆為一個個獨立的 JS 檔,而非全都放在 app.js 這個主檔案裏。然後,在第一次載入網站時,瀏覽器在載入完當前頁面後,才會使用 prefetch 來載入目前還沒需要用到的 JS 檔案。
懶加載元件就是載入頁面時,不會馬上載入元件,而是等到需要時才載入。例如 Modal、Tooltip 等這些元素不會馬上顯示在頁面裏,而是等到使用者與網頁互動時才會出現,因此適合使用懶加載。
以 About 頁面懶載入 Img 元件為例,當按下按鈕後才顯示 Img 元件。
先講 Vue 2,做法就是使用函式載入元件。注意,以下方法無法在 Vue 3 使用,Vue 3 需要用新語法defineAsyncComponent
,否則會報錯。
Img.vue
<template>
<img src="https://images.unsplash.com/photo-1633479585706-69b9f3cc34aa?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwxMHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=700&q=60" alt="">
</template>
About.vue
<template>
<div class="about">
<h1>This is an about page</h1>
<button @click="show = !show"> Show Img </button>
<div v-if="show">
<Img />
</div>
</div>
</template>
export default {
components: {
// 為了清晰看到檔名,這裏使用 webpackChunkName 指定檔名
// 以下會回傳 Promise
Img: () => import(/* webpackChunkName: "Img" */'@/components/Img.vue')
},
data() {
return {
show: false,
}
}
}
如果要進階設定其他 option,例如載入時的 loading 元件、timeout 限制等,就使用物件來寫:
// 自行建立 Loading 元件
import Loading from '@/components/Loading.vue'
const Img = () => ({
component: import(/* webpackChunkName: "Img" */'@/components/Img.vue'),
loading: Loading,
timeout: 3000
})
export default {
components: {
Img
},
data() {
return {
show: false,
}
}
}
進入 About 頁面:
按按鈕顯示 Img 元件:
直接載入 prefetch cache 的 Img.js 以及 disk cache 的圖片。
Vue 3 需要使用 defineAsyncComponent
來實現懶載入元件。
About.vue
<template>
<div class="about">
<h1>This is an about page</h1>
<button @click="show = !show"> Show Img </button>
<div v-if="show">
<Img />
</div>
</div>
</template>
const Img = defineAsyncComponent( () => import(/* webpackChunkName: "Img" */'@/components/Img.vue'))
export default {
components: {
Img
},
data() {
return {
show: false,
}
}
}
如果要進階設定其他 option:
// 自行建立 Loading 元件
import Loading from '@/components/Loading.vue'
import { defineAsyncComponent } from 'vue'
const Img = defineAsyncComponent( {
// 此屬性在 Vue 2 命為 component
// 為了清晰看到檔名,這裏使用 webpackChunkName 指定檔名
loader: () => import(/* webpackChunkName: "Img" */'@/components/Img.vue'),
delay: 2000,
timeout: 3000,
loadingComponent: Loading
})
export default {
components: {
Img,
Loading
},
data() {
return {
show: false
}
}
}
結果如同上面示範過的 Vue 2。
最後,Vue 3 有新增 Suspense
標籤來處理懶加載元件,做法會稍有不同,但目前此語法仍在試驗階段,不建議在正式專案上使用,在此先省略不作解說。
defineAsyncComponent
來完成懶加載元件。How To Lazy Load Components In Vue.js!
Vue School - Lazy Loading Routes (Vue CLI Only)
Improving performance in your Vue 3 application by Lazy Loading components
A Deep Dive Into Async Components In Vue! (Should You Use This?)