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

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

初始加載資源過多

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

初始加載了155個請求
初始加載了155個請求

初始加載的時候,一共請求了155個資源,請求的瀑布圖就快要和頁面同樣長了😊前端

請求概況
請求概況

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

PerformanceNavigationTiming 信息
PerformanceNavigationTiming 信息

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

請求資源類型分佈
請求資源類型分佈

問題分析

頁面結構拆分
頁面結構拆分

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

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

解決思路

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

組件化分治思想

爲了方便後續的優化,咱們必需要求每一個模塊之間下降耦合,將相關的邏輯(好比請求接口、請求相關的依賴資源)都封裝在內部,在 Vue 裏落實成組件的形式。web

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

加載優先級

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

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

有了上面的解決思路,咱們開始思考具體的實現:瀏覽器

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

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

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

http://caniuse.com/#search=IntersectionObserver
http://caniuse.com/#search=IntersectionObserver

目前是現代瀏覽器支持,低版本瀏覽器能夠經過 polyfill 兼容。

如何儘量懶的條件渲染?

在解決了加載條件的判斷以後,咱們須要解決加載條件爲假的狀況下不去渲染、加載條件爲真的時候才渲染的問題,這裏的答案很是簡單:使用 Vue.js 提供的 v-if 指令,就能夠作到真正的惰性渲染。

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

若是在判斷加載條件爲假的時候,什麼都不渲染,就會帶來一系列問題:

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

這裏引入一個骨架屏的概念,咱們爲真實的組件作一個在尺寸、樣式上很是接近真實組件的組件,叫作骨架屏。

骨架屏
骨架屏

骨架屏的做用有:

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

如何提高切換時的體驗?

在真實組件開始渲染的時候,須要必定的時間和空間,時間指的是真實組件從建立到渲染的時間,包括請求接口、請求資源和渲染的時間,空間指的是頁面佈局中須要給真實組件留出恰好的位置,避免產生抖動。

這裏咱們可使用 Vue.js 內置的 transition 組件自定義骨架組件和真實組件的進入和離開效果,經過合理的佈局和定位,減小切換時的抖動,
經過設置過渡效果給真實組件留出必定的加載時間。

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

Vue組件懶加載方案介紹

項目Github地址: github.com/xunleif2e/v…

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

特性

  • 支持 組件可見或即將可見時懶加載
  • 支持 組件延時加載
  • 支持 加載組件前展現組件骨架,提升用戶體驗
  • 支持 懶加載組件分包異步加載

安裝和使用

npm i @xunlei/vue-lazy-component複製代碼
  • 方式1 利用插件方式全局註冊
  • 方式2 局部註冊
  • 方式3 獨立版本引入,自動全局註冊

用法

使用方式
使用方式

Props

參數 說明 類型 可選值 默認值
viewport 組件所在的視口,若是組件是在頁面容器內滾動,視口就是該容器 HTMLElement true null,表明視窗
direction 視口的滾動方向, vertical表明垂直方向,horizontal表明水平方向 String true vertical
threshold 預加載閾值, css單位 String true 0px
tagName 包裹組件的外層容器的標籤名 String true div
timeout 等待時間,若是指定了時間,不論可見與否,在指定時間以後自動加載 Number true -

Events

事件名 說明 事件參數
before-init 模塊可見或延時截止致使準備開始加載懶加載模塊 -
init 開始加載懶加載模塊,此時骨架組件開始消失 -
before-enter 懶加載模塊開始進入 el
before-leave 骨架組件開始離開 el
after-leave 骨架組件已經離開 el
after-enter 懶加載模快已經進入 el
after-init 初始化完成 -

DEMO 1 超長頁面懶加載

xunleif2e.github.io/vue-lazy-co…

<vue-lazy-component>
    <st-series-sohu/>
    <st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>複製代碼

經過上面這種簡單的使用方式就能夠實現組件即將可見時自動加載。

DEMO 2 延時加載

xunleif2e.github.io/vue-lazy-co…

延時加載
延時加載

<vue-lazy-component :timeout="1000">
    <st-series-sohu/>
    <st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>
`複製代碼

若是有時候僅僅是但願某些組件稍後渲染,而不必定要等到可見時,能夠經過這種方式。

好比咱們業務中可能會有些運營性質的掛件,就能夠採起延時加載的方式。

DEMO 3 自定義過渡效果

xunleif2e.github.io/vue-lazy-co…

自定義過渡效果
自定義過渡效果

若是以爲 Vue Lazy Component 自帶的淡入淡出的過渡效果太醜,或者須要調整淡入淡出效果的時長,就能夠經過自定義樣式來改變過渡效果。這個例子演示了另一種過渡效果,transition 的生命週期能夠參考 Vue.js 的 transition 組件的文檔。

DEMO 4 webpack 分包

xunleif2e.github.io/vue-lazy-co…

懶加載組件異步加載
懶加載組件異步加載

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-lazy-co…

IM場景
IM場景

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

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

應用效果

首先 Vue Lazy Component 的設計雖然是說組件級的,其實它的粒度可大可小,大的好比頁面不一樣的區域,小的就像 DEMO5 裏的只是一個用戶頭像,因此適用性很是強,只要有懶加載需求的場景基本均可以採用。

另外,在終端方面,不只能夠兼容PC端的項目,在移動端也是可使用的,固然,須要解決 IntersectionObserver API 的兼容性問題,在項目 Readme 裏提到了 w3c 的 polyfill 的地址。

應用業務

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

迅雷前端項目
迅雷前端項目

優化後請求瀑布圖

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

請求數變爲31個
請求數變爲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/v…


掃一掃關注迅雷前端公衆號

相關文章
相關標籤/搜索