iT邦幫忙

2021 iThome 鐵人賽

DAY 27
0
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 27

不只懂 Vue 語法:為何懶加載路由和元件會提升網頁效能?

問題回答

懶加載路由或元件的意思是當訪問該路由,或需要顯示該元件時,才載入該路由或元件。這做法會提升網頁效能,在打包時,如果某路由或元件設定了懶加載,就會獨立被拆成一個 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 檢查:

  • app.js 就是整個專案所用到的 JavaScript,目前是 157KB。
  • 因為 About 頁使用了懶加載,所以獨立拆成一個 JS 檔案。

使用 prefetch 實現懶加載

但既然 About 是懶加載,為何現在一打開首頁,就馬上載入 about.js?打開 about.js,會發現瀏覽器使用 prefetch 來載入 About。

Vue 官方文件有說明 prefetch 的意思:

<link rel="prefetch"> 是一种 resource hint,用来告诉浏览器在页面加载完成后,利用空闲时间提前获取用户未来可能会访问的内容。

所以,Vue 的確有實現懶加載,做法就是以上提到,等當前頁面的檔案都載入完成後,才會在背後開始載入那些之後有需要用到的檔案。

你也可以打開 page source 檢查原始碼,看到 Vue 目前以 prefetch 方法載入 about.js

使用懶加載載入 Products

接著,現在嘗試把 Products 轉用懶加載。看看與以上的檔案大小差別。

{
    path: '/products',
    name: 'Products',
    // webpackChunkName 是產生獨立 JS 檔時的檔名
    component: () => import(/* webpackChunkName: "products" */ '../views/Products.vue')
}

結果:

  • app.js 現在是 142KB(之前是 157KB)。
  • 產生了一個 products.js 檔案。之所以名為 products.js 是因為在 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 輸出。

當訪問 About 頁時,只須載入在首頁 prefetch 好的 about.js

以上所有測試都是在首頁 http://localhost:8080/#/ 進行。當訪問 About 頁時,結果會怎樣?

結果:

如果點擊 All tab 來看,會發現有兩個 about.js,第一個是 prefetch 載入,第二個只是 prefetch cache 而己:

點擊 JS tab 會更清楚。當訪問 /about 頁時,其實只是載入之前已在首頁 prefetch 取得的 about.js 而己:

小總結:為何懶加載路由可提升效能?

總結來說,以上測試得知,使用懶加載路由可以縮減第一次載入網站時的等待時間。使用懶加載把頁面分拆為一個個獨立的 JS 檔,而非全都放在 app.js 這個主檔案裏。然後,在第一次載入網站時,瀏覽器在載入完當前頁面後,才會使用 prefetch 來載入目前還沒需要用到的 JS 檔案。

那什麼是懶加載元件?

懶加載元件就是載入頁面時,不會馬上載入元件,而是等到需要時才載入。例如 Modal、Tooltip 等這些元素不會馬上顯示在頁面裏,而是等到使用者與網頁互動時才會出現,因此適合使用懶加載。

Vue 2 使用動態載入方式

以 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

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 標籤來處理懶加載元件,做法會稍有不同,但目前此語法仍在試驗階段,不建議在正式專案上使用,在此先省略不作解說。

總結

  • 建議全部路由都使用懶加載。 並看該元件的功能性質來決定是否需要使用懶加載元件。
  • app.js 是整個 Vue 應用程式打包後的核心檔案,第一次載入頁面時,此檔案的大小會影響載入網站的速度。
  • Vue 使用 Webpack 的 code splitting 功能,把設定了懶加載的元件或路由,拆分成獨立一個 JS 檔案,因此不會把它打包進去主檔案 app.js 裏。在第一次載入網站時,這做法能縮短載入網站的時間。
  • Vue 使用 prefetch 來下載獨立打包出來的 JS 檔案。意思是當前頁面內容已下載完成後,才會下載這些檔案。之後,當使用者訪問這些設了懶加載的頁面或元件時,就會以 prefetch cache 的形成載入它們。
  • Vue 2 在 component 使用函式來設定懶加載元件。但此做法目前沒法用在 Vue 3。Vue 3 需要使用新語法 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?)


上一篇
不只懂 Vue 語法:試解釋嵌套路由與嵌套命名視圖的概念?
下一篇
不只懂 Vue 語法:試說明有哪些方式可以全域註冊方法?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31

尚未有邦友留言

立即登入留言