性能優化之組件懶載入: Vue Lazy Component 介紹

這篇文章分享了從遇到前端業務性能問題,到分析、解決並且梳理通用的Vue 2.x 組件級懶載入解決方案(Vue Lazy Component )的過程。

初始載入資源過多

問題起源於我們的一個頁面,下面是這個頁面的截圖和初次請求的瀑布圖。

初始載入的時候,一共請求了155個資源,請求的瀑布圖就快要和頁面一樣長了??

初始載入的資源過多導致在 domInteractive 之後,頁面花費了大量時間載入子資源,導致頁面的 load 時長被嚴重拖長,達到了 5.6s 。

來看看這些子資源都是什麼,根據請求資源的類型,我們找到了最多的類型是圖片,這是顯而易見的,頁面上到處都是大圖片,其次是 js 文件,由第三方的業務插件和一些 JSONP 的介面組成。

問題分析

再回到最初的這個頁面,結合上面的數據情況我們得出了這個頁面的問題總結結論:

  • 頁面由大量模塊組成
  • 每個模塊部分由首頁自主維護,部分由業務方通過插件維護
  • 所有模塊是同時進行載入
  • 模塊中圖片內容較多
  • 每個模塊的依賴資源較多(包括js文件、介面文件、css文件等)

解決思路

我們提出了下面兩個主要的解決思路:

組件化分治思想

為了方便後續的優化,我們必須要求每個模塊之間降低耦合,將相關的邏輯(比如請求介面、請求相關的依賴資源)都封裝在內部,在 Vue 里落實成組件的形式。

  • 將各模塊拆分為組件粒度
  • 將組件依賴的資源全部封裝在組件內部進行調用

載入優先順序

在完成了組件化的拆分,確保模塊之間不會互相影響和產生耦合之後,我們可以方面地調整載入策略。載入的策略是根據可見性來處理優先順序問題。

  • 優先載入首屏可見模塊
  • 其餘不可見模塊懶載入,待可見或即將可見時載入

有了上面的解決思路,我們開始思考具體的實現:

如何解決判斷可見性問題?

從前我們都是通過監聽滾動事件、resize 事件來判斷模塊是否可見,代碼不僅繁瑣,而且一不小心沒有函數去抖就又可能導致嚴重的性能問題。

現在我們有了更好的選擇—— IntersectionObserver API ,IntersectionObserver 允許你配置一個回調函數,每當 target ,元素和設備視口或者其他指定元素髮生交集的時候該回調函數將會被執行。這個 API 的設計是非同步的,而且保證你的回調執行次數是非常有限的,而且回調是會在主線程空閑時才執行,在性能方面表現更優,使用起來也更簡單。

目前是現代瀏覽器支持,低版本瀏覽器可以通過 polyfill 兼容。

如何儘可能懶的條件渲染?

在解決了載入條件的判斷之後,我們需要解決載入條件為假的情況下不去渲染、載入條件為真的時候才渲染的問題,這裡的答案非常簡單:使用 Vue.js 提供的 v-if 指令,就可以做到真正的惰性渲染。

如果可見後進行初始渲染,可見前如何顯示?

如果在判斷載入條件為假的時候,什麼都不渲染,就會帶來一系列問題:

  • 用戶體驗比較差,最開始是白屏,然後突然又渲染出現內容。
  • 最致命的是我們判斷可見性是需要一個目標來觀察的,如果什麼不都渲染,我們就無從觀察。

這裡引入一個骨架屏的概念,我們為真實的組件做一個在尺寸、樣式上非常接近真實組件的組件,叫做骨架屏。

骨架屏的作用有:

  • 提升用戶感知體驗
  • 保證切換的一致性
  • 提供可見性觀察的目標對象

如何提升切換時的體驗?

在真實組件開始渲染的時候,需要一定的時間和空間,時間指的是真實組件從創建到渲染的時間,包括請求介面、請求資源和渲染的時間,空間指的是頁面布局中需要給真實組件留出剛好的位置,避免產生抖動。

這裡我們可以使用 Vue.js 內置的 transition 組件自定義骨架組件和真實組件的進入和離開效果,通過合理的布局和定位,減少切換時的抖動,

通過設置過渡效果給真實組件留出一定的載入時間。

上面的問題都有了答案之後,我們很容易就可以實現一個通用的方案,來解決組件的懶載入問題。

Vue組件懶載入方案介紹

項目Github地址: github.com/xunleif2e/vu

這個是我們基於上面的思考做的一個通用的解決方案,下面簡單介紹一下特性、使用以及 API 方面的知識,後面結合 5 個具體的 DEMO 來講解更高級的用法。

特性

  • 支持 組件可見或即將可見時懶載入
  • 支持 組件延時載入
  • 支持 載入組件前展示組件骨架,提高用戶體驗
  • 支持 懶載入組件分包非同步載入

安裝和使用

npm i @xunlei/vue-lazy-componentn

  • 方式1 利用插件方式全局註冊
  • 方式2 局部註冊
  • 方式3 獨立版本引入,自動全局註冊

用法

Props

Events

DEMO 1 超長頁面懶載入

xunleif2e.github.io/vue

<vue-lazy-component>n <st-series-sohu/>n <st-series-sohu-skeleton slot="skeleton"/>n</vue-lazy-component>n

通過上面這種簡單的使用方式就可以實現組件即將可見時自動載入。

DEMO 2 延時載入

xunleif2e.github.io/vue

<vue-lazy-component :timeout="1000">n <st-series-sohu/>n <st-series-sohu-skeleton slot="skeleton"/>n</vue-lazy-component>n`n

如果有時候僅僅是希望某些組件稍後渲染,而不一定要等到可見時,可以通過這種方式。

比如我們業務中可能會有些運營性質的掛件,就可以採取延時載入的方式。

DEMO 3 自定義過渡效果

xunleif2e.github.io/vue

如果覺得 Vue Lazy Component 自帶的淡入淡出的過渡效果太丑,或者需要調整淡入淡出效果的時長,就可以通過自定義樣式來改變過渡效果。這個例子演示了另外一種過渡效果,transition 的生命周期可以參考 Vue.js 的 transition 組件的文檔。

DEMO 4 webpack 分包

xunleif2e.github.io/vue

DEMO1 演示了如何懶載入模塊,但其實只是推遲了模塊的渲染和模塊內的資源的載入,如果我們需要更進一步,連模塊本身的代碼也是懶載入,就像 AMD 那樣非同步按需載入,這個也是可以做到的。

這裡可以利用Vue.js的非同步組件,將每個真實組件都註冊成非同步組件,在非同步組件的工廠函數里使用 Webpack 的 AMD 版本的 require ,就可以實現真實組件可以分成獨立的 bundle 載入,脫離頁面 js 的bundle。

但是這裡會有個問題,就算模塊是可見時才渲染,在打開頁面的時候會發現模塊不可見之前它的 bundle 已經載入了,這並沒有實現按需載入。

這個例子演示了一種做法,Vue Lazy Component 可以在即將切換真實組件前通過 Scoped Slots 傳遞一個 loading 屬性給真實組件,真實組件只要是根據這個 loading 來條件渲染就可以避免非按需載入,這個和 Vue.js 對組件的解析機制有關,例子里有相應的的代碼,有興趣的同學可以深入研究下。

DEMO 5 特定視口內懶載入

xunleif2e.github.io/vue

在某些場景下,我們要解決滾動容器內的組件懶載入,這個時候可見性是相對與這個視口來的,這個例子演示了如何指定聊天窗口作為觀察的視口。

這裡吐槽下Vue.js的 $parent 、$refs 的設計,它們都不是響應式的,如果需要動態獲取這些組件引用上的 $el ,必須要等到 mounted 事件發生之後,所以例子的代碼稍微有一點繁瑣。

應用效果

首先 Vue Lazy Component 的設計雖然是說組件級的,其實它的粒度可大可小,大的比如頁面不同的區域,小的就像 DEMO5 里的只是一個用戶頭像,所以適用性非常強,只要有懶載入需求的場景基本都可以採用。

另外,在終端方面,不僅可以兼容PC端的項目,在移動端也是可以使用的,當然,需要解決 IntersectionObserver API 的兼容性問題,在項目 Readme 里提到了 w3c 的 polyfill 的地址。

應用業務

我們目前應用在迅雷的兩個項目中,一個是 PC 迅雷的首頁項目,一個是 PC 迅雷的組隊加速項目,後期預計會推廣到更多的業務中去。

優化後請求瀑布圖

我們再來看看開始那個頁面的情況,在使用了 組件懶載入技術後,請求數變成了只有 31 個,瀑布圖變得比較短了。

數據對比

我們把前後的數據進行一個對比:

  • 請求數變成之前的 1 / 5,優化效果比較明顯
  • 請求大小相比之前降低不太明顯
  • load 時長也同樣不太明顯

分析主要是有較多圖片未按照使用尺寸裁剪和壓縮,導致請求大小較大,同時造成了 load 時長的拖長。

後續性能優化方向

後續我們會繼續來優化這個頁面,主要的方向有兩個:

圖片尺寸適配和壓縮

通過圖片的裁剪和壓縮,解決請求資源大小較大子資源載入時間較長導致 load 時間拖長的問題

預渲染

採用預渲染插件將頁面的主要 css 、js 進行內聯,將骨架架屏通過預渲染生成出來,這樣可以避免 SPA 首屏可見關鍵路徑較長的問題,在頁面解析完 dom 樹以後即可保證首屏可見。

懶載入方案 ROADMAP

Vue Lazy Component 懶載入方案還有些地方做得還不夠好,計劃在後期的幾個小版本里支持以下的特性:

  • SSR 支持 v1.1.0
  • UI單元測試 v1.2.0
  • 減少性能開銷 v1.3.0
    • 重繪
    • FPS

後記

這篇文章分享了從遇到業務實際性能問題,到分析、解決並梳理出通用的解決方案的過程,重點其實不是最終的實現代碼實現,而是解決問題的角度和過程。

最後歡迎大家通過提交 issue 或者 PR 的方式參與貢獻,項目 Github 地址: github.com/xunleif2e/vu


微信搜索 蜂鳥前端 關注迅雷前端公眾號n

推薦閱讀:

該把JS文件放在HTML文檔的那個位置
解密Angular WebWorker Renderer (一)
2D圓形隨機分布
興趣部落的前端性能優化實踐概覽

TAG:前端开发 | 前端性能优化 | Vuejs |