做(zuò)自(zì)由與創造的先行(xíng)者

性能(néng)優化

Vue.js中文手冊

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

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

頁面加載性能(néng):首次訪問(wèn)時(shí),應用展示出內(nèi)容與達到可(kě)交互狀态的速度。這(zhè)通(tōng)常會用 Google 所定義的一(yī)系列 Web 指标 (Web Vitals) 來進行(xíng)衡量,如(rú)最大內(nèi)容繪制 (Largest Contentful Paint,縮寫為(wèi) LCP) 和(hé)首次輸入延遲 (First Input Delay,縮寫為(wèi) FID)。

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

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

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

Jason Miller 在 Application Holotypes 一(yī)文中讨論了(le) Web 應用的類型以及它們各自(zì)的理想實現/交付方式。

分析選項 ​

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

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

PageSpeed Insights

WebPageTest

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

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

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

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

頁面加載優化 ​

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

選用正确的架構 ​

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

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

包體積與 Tree-shaking 優化 ​

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

盡可(kě)能(néng)地(dì)采用構建步驟

如(rú)果使用的是相對現代的打包工(gōng)具,許多 Vue 的 API 都(dōu)是可(kě)以被 tree-shake 的。舉例來說,如(rú)果你根本沒有使用到內(nèi)置的 <Transition> 組件,它将不會被打包進入最終的産物(wù)裏。Tree-shaking 也可(kě)以移除你源代碼中其他(tā)未使用到的模塊。

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

在引入新的依賴項時(shí)要(yào)小心包體積膨脹!在現實的應用中,包體積膨脹通(tōng)常因為(wèi)無意識地(dì)引入了(le)過重的依賴導緻的。

如(rú)果使用了(le)構建步驟,應當盡量選擇提供 ES 模塊格式的依賴,它們對 tree-shaking 更友(yǒu)好。舉例來說,選擇 lodash-es 比 lodash 更好。

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

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

代碼分割 ​

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

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

js

// lazy.js 及其依賴會被拆分到一(yī)個單獨的文件中

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

function loadLazy() {

return import('./lazy.js')

}

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

js

import { defineAsyncComponent } from 'vue'

// 會為(wèi) Foo.vue 及其依賴創建單獨的一(yī)個塊

// 它隻會按需加載

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

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

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

更新優化 ​

Props 穩定性 ​

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

template

<ListItem

v-for="item in list"

:id="item.id"

:active-id="activeId" />

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

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

template

<ListItem

v-for="item in list"

:id="item.id"

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

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

v-once ​

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

v-memo ​

v-memo 是一(yī)個內(nèi)置指令,可(kě)以用來有條件地(dì)跳(tiào)過某些大型子樹(shù)或者 v-for 列表的更新。查看(kàn)它的 API 參考手冊可(kě)以了(le)解更多細節。

通(tōng)用優化 ​

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

大型虛拟列表 ​

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

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

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

vue-virtual-scroller

vue-virtual-scroll-grid

vueuc/VVirtualList

減少(shǎo)大型不可(kě)變數(shù)據的響應性開(kāi)銷 ​

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

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

js

const shallowArray = shallowRef([

/* 巨大的列表,裏面包含深層的對象 */

])

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

shallowArray.value.push(newObject)

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

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

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

shallowArray.value[0].foo = 1

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

shallowArray.value = [

{

...shallowArray.value[0],

foo: 1

},

...shallowArray.value.slice(1)

]

避免不必要(yào)的組件抽象 ​

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

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

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