這篇文章分享了從遇到前端業務性能問題,到分析、解決而且梳理出通用的Vue 2.x 組件級懶加載解決方案(Vue Lazy Component )的過程。css
問題起源於咱們的一個頁面,下面是這個頁面的截圖和初次請求的瀑布圖。html
初始加載的時候,一共請求了155個資源,請求的瀑布圖就快要和頁面同樣長了😊前端
初始加載的資源過多致使在 domInteractive 以後,頁面花費了大量時間加載子資源,致使頁面的 load 時長被嚴重拖長,達到了 5.6s 。vue
來看看這些子資源都是什麼,根據請求資源的類型,咱們找到了最多的類型是圖片,這是顯而易見的,頁面上處處都是大圖片,其次是 js 文件,由第三方的業務插件和一些 JSONP 的接口組成。webpack
再回到最初的這個頁面,結合上面的數據狀況咱們得出了這個頁面的問題總結結論:git
咱們提出了下面兩個主要的解決思路:github
爲了方便後續的優化,咱們必需要求每一個模塊之間下降耦合,將相關的邏輯(好比請求接口、請求相關的依賴資源)都封裝在內部,在 Vue 裏落實成組件的形式。web
在完成了組件化的拆分,確保模塊之間不會互相影響和產生耦合以後,咱們能夠方面地調整加載策略。加載的策略是根據可見性來處理優先級問題。npm
有了上面的解決思路,咱們開始思考具體的實現:瀏覽器
從前咱們都是經過監聽滾動事件、resize 事件來判斷模塊是否可見,代碼不只繁瑣,並且一不當心沒有函數去抖就又可能致使嚴重的性能問題。
如今咱們有了更好的選擇—— IntersectionObserver API ,IntersectionObserver 容許你配置一個回調函數,每當 target ,元素和設備視口或者其餘指定元素髮生交集的時候該回調函數將會被執行。這個 API 的設計是異步的,並且保證你的回調執行次數是很是有限的,並且回調是會在主線程空閒時才執行,在性能方面表現更優,使用起來也更簡單。
目前是現代瀏覽器支持,低版本瀏覽器能夠經過 polyfill 兼容。
在解決了加載條件的判斷以後,咱們須要解決加載條件爲假的狀況下不去渲染、加載條件爲真的時候才渲染的問題,這裏的答案很是簡單:使用 Vue.js 提供的 v-if 指令,就能夠作到真正的惰性渲染。
若是在判斷加載條件爲假的時候,什麼都不渲染,就會帶來一系列問題:
這裏引入一個骨架屏的概念,咱們爲真實的組件作一個在尺寸、樣式上很是接近真實組件的組件,叫作骨架屏。
骨架屏的做用有:
在真實組件開始渲染的時候,須要必定的時間和空間,時間指的是真實組件從建立到渲染的時間,包括請求接口、請求資源和渲染的時間,空間指的是頁面佈局中須要給真實組件留出恰好的位置,避免產生抖動。
這裏咱們可使用 Vue.js 內置的 transition 組件自定義骨架組件和真實組件的進入和離開效果,經過合理的佈局和定位,減小切換時的抖動,
經過設置過渡效果給真實組件留出必定的加載時間。
上面的問題都有了答案以後,咱們很容易就能夠實現一個通用的方案,來解決組件的懶加載問題。
項目Github地址: github.com/xunleif2e/v…
這個是咱們基於上面的思考作的一個通用的解決方案,下面簡單介紹一下特性、使用以及 API 方面的知識,後面結合 5 個具體的 DEMO 來說解更高級的用法。
npm i @xunlei/vue-lazy-component複製代碼
參數 | 說明 | 類型 | 可選值 | 默認值 |
---|---|---|---|---|
viewport | 組件所在的視口,若是組件是在頁面容器內滾動,視口就是該容器 | HTMLElement | true | null ,表明視窗 |
direction | 視口的滾動方向, vertical 表明垂直方向,horizontal 表明水平方向 |
String | true | vertical |
threshold | 預加載閾值, css單位 | String | true | 0px |
tagName | 包裹組件的外層容器的標籤名 | String | true | div |
timeout | 等待時間,若是指定了時間,不論可見與否,在指定時間以後自動加載 | Number | true | - |
事件名 | 說明 | 事件參數 |
---|---|---|
before-init | 模塊可見或延時截止致使準備開始加載懶加載模塊 | - |
init | 開始加載懶加載模塊,此時骨架組件開始消失 | - |
before-enter | 懶加載模塊開始進入 | el |
before-leave | 骨架組件開始離開 | el |
after-leave | 骨架組件已經離開 | el |
after-enter | 懶加載模快已經進入 | el |
after-init | 初始化完成 | - |
xunleif2e.github.io/vue-lazy-co…
<vue-lazy-component>
<st-series-sohu/>
<st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>複製代碼
經過上面這種簡單的使用方式就能夠實現組件即將可見時自動加載。
xunleif2e.github.io/vue-lazy-co…
<vue-lazy-component :timeout="1000">
<st-series-sohu/>
<st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>
`複製代碼
若是有時候僅僅是但願某些組件稍後渲染,而不必定要等到可見時,能夠經過這種方式。
好比咱們業務中可能會有些運營性質的掛件,就能夠採起延時加載的方式。
xunleif2e.github.io/vue-lazy-co…
若是以爲 Vue Lazy Component 自帶的淡入淡出的過渡效果太醜,或者須要調整淡入淡出效果的時長,就能夠經過自定義樣式來改變過渡效果。這個例子演示了另一種過渡效果,transition 的生命週期能夠參考 Vue.js 的 transition 組件的文檔。
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 對組件的解析機制有關,例子裏有相應的的代碼,有興趣的同窗能夠深刻研究下。
xunleif2e.github.io/vue-lazy-co…
在某些場景下,咱們要解決滾動容器內的組件懶加載,這個時候可見性是相對與這個視口來的,這個例子演示瞭如何指定聊天窗口做爲觀察的視口。
這裏吐槽下Vue.js的 $parent 、$refs 的設計,它們都不是響應式的,若是須要動態獲取這些組件引用上的 $el ,必需要等到 mounted 事件發生以後,因此例子的代碼稍微有一點繁瑣。
首先 Vue Lazy Component 的設計雖然是說組件級的,其實它的粒度可大可小,大的好比頁面不一樣的區域,小的就像 DEMO5 裏的只是一個用戶頭像,因此適用性很是強,只要有懶加載需求的場景基本均可以採用。
另外,在終端方面,不只能夠兼容PC端的項目,在移動端也是可使用的,固然,須要解決 IntersectionObserver API 的兼容性問題,在項目 Readme 裏提到了 w3c 的 polyfill 的地址。
咱們目前應用在迅雷的兩個項目中,一個是 PC 迅雷的首頁項目,一個是 PC 迅雷的組隊加速項目,後期預計會推廣到更多的業務中去。
咱們再來看看開始那個頁面的狀況,在使用了 組件懶加載技術後,請求數變成了只有 31 個,瀑布圖變得比較短了。
咱們把先後的數據進行一個對比:
分析主要是有較多圖片未按照使用尺寸裁剪和壓縮,致使請求大小較大,同時形成了 load 時長的拖長。
後續咱們會繼續來優化這個頁面,主要的方向有兩個:
經過圖片的裁剪和壓縮,解決請求資源大小較大子資源加載時間較長致使 load 時間拖長的問題
採用預渲染插件將頁面的主要 css 、js 進行內聯,將骨架架屏經過預渲染生成出來,這樣能夠避免 SPA 首屏可見關鍵路徑較長的問題,在頁面解析完 dom 樹之後便可保證首屏可見。
Vue Lazy Component 懶加載方案還有些地方作得還不夠好,計劃在後期的幾個小版本里支持如下的特性:
這篇文章分享了從遇到業務實際性能問題,到分析、解決並梳理出通用的解決方案的過程,重點其實不是最終的實現代碼實現,而是解決問題的角度和過程。
最後歡迎你們經過提交 issue 或者 PR 的方式參與貢獻,項目 Github 地址: github.com/xunleif2e/v… 。