這篇文章分享了從遇到前端業務效能問題,到分析、解決並且梳理出通用的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/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…
在某些場景下,我們要解決滾動容器內的元件懶載入,這個時候可見性是相對與這個視口來的,這個例子演示瞭如何指定聊天視窗作為觀察的視口。
這裡吐槽下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/v… 。