做自(zì)由與創造的先行者

性能(néng)優化

Vue.js中文(wén)手冊

Vue 在大(dà)多數常見場景下(xià)性能(néng)都是很(hěn)優秀的,通常不需要手動優化。然而,總會(huì)有一些(xiē)具有挑戰性的場景需要進行針對(duì)性的微調。在本節中,我們将讨論用(yòng) Vue 開(kāi)發的應用(yòng)在性能(néng)方面該注意些(xiē)什(shén)麽。

首先,讓我們區(qū)分一下(xià) web 應用(yòng)性能(néng)的兩個主要方面:

頁面加載性能(néng):首次訪問時(shí),應用(yòng)展示出内容與達到(dào)可交互狀态的速度。這(zhè)通常會(huì)用(yòng) Google 所定義的一系列 Web 指标 (Web Vitals) 來(lái)進行衡量,如最大(dà)内容繪制 (Largest Contentful Paint,縮寫爲 LCP) 和(hé)首次輸入延遲 (First Input Delay,縮寫爲 FID)。

更新性能(néng):應用(yòng)響應用(yòng)戶輸入更新的速度。比如當用(yòng)戶在搜索框中輸入時(shí)結果列表的更新速度,或者用(yòng)戶在一個單頁面應用(yòng) (SPA) 中點擊鏈接跳轉頁面時(shí)的切換速度。

雖然最理(lǐ)想的情況是将兩者都最大(dà)化,但(dàn)是不同的前端架構往往會(huì)影響到(dào)在這(zhè)些(xiē)方面是否能(néng)達到(dào)更理(lǐ)想的性能(néng)。此外(wài),你(nǐ)所構建的應用(yòng)的類型極大(dà)地影響了(le)你(nǐ)在性能(néng)方面應該優先考慮的問題。因此,優化性能(néng)的第一步是爲你(nǐ)的應用(yòng)類型确定合适的架構:

查看(kàn)使用(yòng) Vue 的多種方式這(zhè)一章看(kàn)看(kàn)如何用(yòng)不同的方式圍繞 Vue 組織架構。

Jason Miller 在 Application Holotypes 一文(wén)中讨論了(le) Web 應用(yòng)的類型以及它們各自(zì)的理(lǐ)想實現(xiàn)/交付方式。

分析選項 ​

爲了(le)提高(gāo)性能(néng),我們首先需要知(zhī)道(dào)如何衡量它。在這(zhè)方面,有一些(xiē)很(hěn)棒的工(gōng)具可以提供幫助:

用(yòng)于生産部署的負載性能(néng)分析:

PageSpeed Insights

WebPageTest

用(yòng)于本地開(kāi)發期間的性能(néng)分析:

Chrome 開(kāi)發者工(gōng)具“性能(néng)”面闆

app.config.performance 将會(huì)開(kāi)啓 Vue 特有的性能(néng)标記,标記在 Chrome 開(kāi)發者工(gōng)具的性能(néng)時(shí)間線上(shàng)。

Vue 開(kāi)發者擴展也(yě)提供了(le)性能(néng)分析的功能(néng)。

頁面加載優化 ​

頁面加載優化有許多跟框架無關的方面 - 這(zhè)份 web.dev 指南提供了(le)一個全面的總結。這(zhè)裏,我們将主要關注和(hé) Vue 相關的技巧。

選用(yòng)正确的架構 ​

如果你(nǐ)的用(yòng)例對(duì)頁面加載性能(néng)很(hěn)敏感,請(qǐng)避免将其部署爲純客戶端的 SPA,而是讓服務器直接發送包含用(yòng)戶想要查看(kàn)的内容的 HTML 代碼。純客戶端渲染存在首屏加載緩慢的問題,這(zhè)可以通過服務器端渲染 (SSR) 或靜态站(zhàn)點生成 (SSG) 來(lái)緩解。查看(kàn) SSR 指南以了(le)解如何使用(yòng) Vue 實現(xiàn) SSR。如果應用(yòng)對(duì)交互性要求不高(gāo),你(nǐ)還可以使用(yòng)傳統的後端服務器來(lái)渲染 HTML,并在客戶端使用(yòng) Vue 對(duì)其進行增強。

如果你(nǐ)的主應用(yòng)必須是 SPA,但(dàn)還有其他(tā)的營銷相關頁面 (落地頁、關于頁、博客等),請(qǐng)單獨部署這(zhè)些(xiē)頁面!理(lǐ)想情況下(xià),營銷頁面應該是包含盡可能(néng)少 JS 的靜态 HTML,并用(yòng) SSG 方式部署。

包體積與 Tree-shaking 優化 ​

一個最有效的提升頁面加載速度的方法就是壓縮 JavaScript 打包産物的體積。當使用(yòng) Vue 時(shí)有下(xià)面一些(xiē)辦法來(lái)減小(xiǎo)打包産物體積:

盡可能(néng)地采用(yòng)構建步驟

如果使用(yòng)的是相對(duì)現(xiàn)代的打包工(gōng)具,許多 Vue 的 API 都是可以被 tree-shake 的。舉例來(lái)說,如果你(nǐ)根本沒有使用(yòng)到(dào)内置的 <Transition> 組件,它将不會(huì)被打包進入最終的産物裏。Tree-shaking 也(yě)可以移除你(nǐ)源代碼中其他(tā)未使用(yòng)到(dào)的模塊。

當使用(yòng)了(le)構建步驟時(shí),模闆會(huì)被預編譯,因此我們無須在浏覽器中載入 Vue 編譯器。這(zhè)在同樣最小(xiǎo)化加上(shàng) gzip 優化下(xià)會(huì)相對(duì)縮小(xiǎo) 14kb 并避免運行時(shí)的編譯開(kāi)銷。

在引入新的依賴項時(shí)要小(xiǎo)心包體積膨脹!在現(xiàn)實的應用(yòng)中,包體積膨脹通常因爲無意識地引入了(le)過重的依賴導緻的。

如果使用(yòng)了(le)構建步驟,應當盡量選擇提供 ES 模塊格式的依賴,它們對(duì) tree-shaking 更友好(hǎo)。舉例來(lái)說,選擇 lodash-es 比 lodash 更好(hǎo)。

查看(kàn)依賴的體積,并評估與其所提供的功能(néng)之間的性價比。如果依賴對(duì) tree-shaking 友好(hǎo),實際增加的體積大(dà)小(xiǎo)将取決于你(nǐ)從(cóng)它之中導入的 API。像 bundlejs.com 這(zhè)樣的工(gōng)具可以用(yòng)來(lái)做快(kuài)速的檢查,但(dàn)是根據實際的構建設置來(lái)評估總是最準确的。

如果你(nǐ)隻在漸進式增強的場景下(xià)使用(yòng) Vue,并想要避免使用(yòng)構建步驟,請(qǐng)考慮使用(yòng) petite-vue (隻有 6kb) 來(lái)代替。

代碼分割 ​

代碼分割是指構建工(gōng)具将構建後的 JavaScript 包拆分爲多個較小(xiǎo)的,可以按需或并行加載的文(wén)件。通過适當的代碼分割,頁面加載時(shí)需要的功能(néng)可以立即下(xià)載,而額外(wài)的塊隻在需要時(shí)才加載,從(cóng)而提高(gāo)性能(néng)。

像 Rollup (Vite 就是基于它之上(shàng)開(kāi)發的) 或者 webpack 這(zhè)樣的打包工(gōng)具可以通過分析 ESM 動态導入的語法來(lái)自(zì)動進行代碼分割:

js

// lazy.js 及其依賴會(huì)被拆分到(dào)一個單獨的文(wén)件中

// 并隻在 `loadLazy()` 調用(yòng)時(shí)才加載

function loadLazy() {

return import('./lazy.js')

}

懶加載對(duì)于頁面初次加載時(shí)的優化幫助極大(dà),它幫助應用(yòng)暫時(shí)略過了(le)那些(xiē)不是立即需要的功能(néng)。在 Vue 應用(yòng)中,這(zhè)可以與 Vue 的異步組件搭配使用(yòng),爲組件樹創建分離的代碼塊:

js

import { defineAsyncComponent } from 'vue'

// 會(huì)爲 Foo.vue 及其依賴創建單獨的一個塊

// 它隻會(huì)按需加載

//(即該異步組件在頁面中被渲染時(shí))

const Foo = defineAsyncComponent(() => import('./Foo.vue'))

對(duì)于使用(yòng)了(le) Vue Router 的應用(yòng),強烈建議(yì)使用(yòng)異步組件作(zuò)爲路由組件。Vue Router 已經顯性地支持了(le)獨立于 defineAsyncComponent 的懶加載。查看(kàn)懶加載路由了(le)解更多細節。

更新優化 ​

Props 穩定性 ​

在 Vue 之中,一個子組件隻會(huì)在其至少一個 props 改變時(shí)才會(huì)更新。思考以下(xià)示例:

template

<ListItem

v-for="item in list"

:id="item.id"

:active-id="activeId" />

在 <ListItem> 組件中,它使用(yòng)了(le) id 和(hé) activeId 兩個 props 來(lái)确定它是否是當前活躍的那一項。雖然這(zhè)是可行的,但(dàn)問題是每當 activeId 更新時(shí),列表中的每一個 <ListItem> 都會(huì)跟着更新!

理(lǐ)想情況下(xià),隻有活躍狀态發生改變的項才應該更新。我們可以将活躍狀态比對(duì)的邏輯移入父組件來(lái)實現(xiàn)這(zhè)一點,然後讓 <ListItem> 改爲接收一個 active prop:

template

<ListItem

v-for="item in list"

:id="item.id"

:active="item.id === activeId" />

現(xiàn)在,對(duì)于大(dà)多數的組件來(lái)說,activeId 改變時(shí),它們的 active prop 都會(huì)保持不變,因此它們無需再更新。總結一下(xià),這(zhè)個技巧的核心思想就是讓傳給子組件的 props 盡量保持穩定。

v-once ​

v-once 是一個内置的指令,可以用(yòng)來(lái)渲染依賴運行時(shí)數據但(dàn)無需再更新的内容。它的整個子樹都會(huì)在未來(lái)的更新中被跳過。查看(kàn)它的 API 參考手冊可以了(le)解更多細節。

v-memo ​

v-memo 是一個内置指令,可以用(yòng)來(lái)有條件地跳過某些(xiē)大(dà)型子樹或者 v-for 列表的更新。查看(kàn)它的 API 參考手冊可以了(le)解更多細節。

通用(yòng)優化 ​

以下(xià)技巧能(néng)同時(shí)改善頁面加載和(hé)更新性能(néng)。

大(dà)型虛拟列表 ​

所有的前端應用(yòng)中最常見的性能(néng)問題就是渲染大(dà)型列表。無論一個框架性能(néng)有多好(hǎo),渲染成千上(shàng)萬個列表項都會(huì)變得很(hěn)慢,因爲浏覽器需要處理(lǐ)大(dà)量的 DOM 節點。

但(dàn)是,我們并不需要立刻渲染出全部的列表。在大(dà)多數場景中,用(yòng)戶的屏幕尺寸隻會(huì)展示這(zhè)個巨大(dà)列表中的一小(xiǎo)部分。我們可以通過列表虛拟化來(lái)提升性能(néng),這(zhè)項技術使我們隻需要渲染用(yòng)戶視(shì)口中能(néng)看(kàn)到(dào)的部分。

要實現(xiàn)列表虛拟化并不簡單,幸運的是,你(nǐ)可以直接使用(yòng)現(xiàn)有的社區(qū)庫:

vue-virtual-scroller

vue-virtual-scroll-grid

vueuc/VVirtualList

減少大(dà)型不可變數據的響應性開(kāi)銷 ​

Vue 的響應性系統默認是深度的。雖然這(zhè)讓狀态管理(lǐ)變得更直觀,但(dàn)在數據量巨大(dà)時(shí),深度響應性也(yě)會(huì)導緻不小(xiǎo)的性能(néng)負擔,因爲每個屬性訪問都将觸發代理(lǐ)的依賴追蹤。好(hǎo)在這(zhè)種性能(néng)負擔通常隻有在處理(lǐ)超大(dà)型數組或層級很(hěn)深的對(duì)象時(shí),例如一次渲染需要訪問 100,000+ 個屬性時(shí),才會(huì)變得比較明(míng)顯。因此,它隻會(huì)影響少數特定的場景。

Vue 确實也(yě)爲此提供了(le)一種解決方案,通過使用(yòng) shallowRef() 和(hé) shallowReactive() 來(lái)繞開(kāi)深度響應。淺層式 API 創建的狀态隻在其頂層是響應式的,對(duì)所有深層的對(duì)象不會(huì)做任何處理(lǐ)。這(zhè)使得對(duì)深層級屬性的訪問變得更快(kuài),但(dàn)代價是,我們現(xiàn)在必須将所有深層級對(duì)象視(shì)爲不可變的,并且隻能(néng)通過替換整個根狀态來(lái)觸發更新:

js

const shallowArray = shallowRef([

/* 巨大(dà)的列表,裏面包含深層的對(duì)象 */

])

// 這(zhè)不會(huì)觸發更新...

shallowArray.value.push(newObject)

// 這(zhè)才會(huì)觸發更新

shallowArray.value = [...shallowArray.value, newObject]

// 這(zhè)不會(huì)觸發更新...

shallowArray.value[0].foo = 1

// 這(zhè)才會(huì)觸發更新

shallowArray.value = [

{

...shallowArray.value[0],

foo: 1

},

...shallowArray.value.slice(1)

]

避免不必要的組件抽象 ​

有些(xiē)時(shí)候我們會(huì)去創建無渲染組件或高(gāo)階組件 (用(yòng)來(lái)渲染具有額外(wài) props 的其他(tā)組件) 來(lái)實現(xiàn)更好(hǎo)的抽象或代碼組織。雖然這(zhè)并沒有什(shén)麽問題,但(dàn)請(qǐng)記住,組件實例比普通 DOM 節點要昂貴得多,而且爲了(le)邏輯抽象創建太多組件實例将會(huì)導緻性能(néng)損失。

需要提醒的是,隻減少幾個組件實例對(duì)于性能(néng)不會(huì)有明(míng)顯的改善,所以如果一個用(yòng)于抽象的組件在應用(yòng)中隻會(huì)渲染幾次,就不用(yòng)操心去優化它了(le)。考慮這(zhè)種優化的最佳場景還是在大(dà)型列表中。想象一下(xià)一個有 100 項的列表,每項的組件都包含許多子組件。在這(zhè)裏去掉一個不必要的組件抽象,可能(néng)會(huì)減少數百個組件實例的無謂性能(néng)消耗。

網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發
下(xià)一篇:無障礙訪問
上(shàng)一篇:生産部署