性能優(yōu)化之組件懶加載: Vue Lazy Component 介紹

初始加載資源過(guò)多

問(wèn)題起源于我們的一個(gè)頁(yè)面拔创,下面是這個(gè)頁(yè)面的截圖和初次請(qǐng)求的瀑布圖。


初始加載了155個(gè)請(qǐng)求

初始加載的時(shí)候陶冷,一共請(qǐng)求了155個(gè)資源儒溉,請(qǐng)求的瀑布圖就快要和頁(yè)面一樣長(zhǎng)了??

請(qǐng)求概況

初始加載的資源過(guò)多導(dǎo)致在 domInteractive 之后,頁(yè)面花費(fèi)了大量時(shí)間加載子資源咨堤,導(dǎo)致頁(yè)面的 load 時(shí)長(zhǎng)被嚴(yán)重拖長(zhǎng)菇篡,達(dá)到了 5.6s 。

PerformanceNavigationTiming 信息

來(lái)看看這些子資源都是什么一喘,根據(jù)請(qǐng)求資源的類(lèi)型驱还,我們找到了最多的類(lèi)型是圖片,這是顯而易見(jiàn)的凸克,頁(yè)面上到處都是大圖片议蟆,其次是 js 文件,由第三方的業(yè)務(wù)插件和一些 JSONP 的接口組成触徐。

請(qǐng)求資源類(lèi)型分布

問(wèn)題分析

頁(yè)面結(jié)構(gòu)拆分

再回到最初的這個(gè)頁(yè)面咪鲜,結(jié)合上面的數(shù)據(jù)情況我們得出了這個(gè)頁(yè)面的問(wèn)題總結(jié)結(jié)論:

  • 頁(yè)面由大量模塊組成
  • 每個(gè)模塊部分由首頁(yè)自主維護(hù),部分由業(yè)務(wù)方通過(guò)插件維護(hù)
  • 所有模塊是同時(shí)進(jìn)行加載
  • 模塊中圖片內(nèi)容較多
  • 每個(gè)模塊的依賴(lài)資源較多(包括js文件撞鹉、接口文件疟丙、css文件等)

解決思路

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

組件化分治思想

為了方便后續(xù)的優(yōu)化颖侄,我們必須要求每個(gè)模塊之間降低耦合,將相關(guān)的邏輯(比如請(qǐng)求接口享郊、請(qǐng)求相關(guān)的依賴(lài)資源)都封裝在內(nèi)部览祖,在 Vue 里落實(shí)成組件的形式。

  • 將各模塊拆分為組件粒度
  • 將組件依賴(lài)的資源全部封裝在組件內(nèi)部進(jìn)行調(diào)用

加載優(yōu)先級(jí)

在完成了組件化的拆分炊琉,確保模塊之間不會(huì)互相影響和產(chǎn)生耦合之后展蒂,我們可以方面地調(diào)整加載策略。加載的策略是根據(jù)可見(jiàn)性來(lái)處理優(yōu)先級(jí)問(wèn)題苔咪。

  • 優(yōu)先加載首屏可見(jiàn)模塊
  • 其余不可見(jiàn)模塊懶加載锰悼,待可見(jiàn)或即將可見(jiàn)時(shí)加載

有了上面的解決思路,我們開(kāi)始思考具體的實(shí)現(xiàn):

如何解決判斷可見(jiàn)性問(wèn)題团赏?

從前我們都是通過(guò)監(jiān)聽(tīng)滾動(dòng)事件箕般、resize 事件來(lái)判斷模塊是否可見(jiàn),代碼不僅繁瑣舔清,而且一不小心沒(méi)有函數(shù)去抖就又可能導(dǎo)致嚴(yán)重的性能問(wèn)題丝里。

現(xiàn)在我們有了更好的選擇—— IntersectionObserver API ,IntersectionObserver 允許你配置一個(gè)回調(diào)函數(shù)体谒,每當(dāng) target 杯聚,元素和設(shè)備視口或者其他指定元素發(fā)生交集的時(shí)候該回調(diào)函數(shù)將會(huì)被執(zhí)行。這個(gè) API 的設(shè)計(jì)是異步的抒痒,而且保證你的回調(diào)執(zhí)行次數(shù)是非常有限的幌绍,而且回調(diào)是會(huì)在主線程空閑時(shí)才執(zhí)行,在性能方面表現(xiàn)更優(yōu)评汰,使用起來(lái)也更簡(jiǎn)單纷捞。

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

目前是現(xiàn)代瀏覽器支持,低版本瀏覽器可以通過(guò) polyfill 兼容被去。

如何盡可能懶的條件渲染主儡?

在解決了加載條件的判斷之后,我們需要解決加載條件為假的情況下不去渲染惨缆、加載條件為真的時(shí)候才渲染的問(wèn)題糜值,這里的答案非常簡(jiǎn)單:使用 Vue.js 提供的 v-if 指令,就可以做到真正的惰性渲染坯墨。

如果可見(jiàn)后進(jìn)行初始渲染寂汇,可見(jiàn)前如何顯示?

如果在判斷加載條件為假的時(shí)候捣染,什么都不渲染骄瓣,就會(huì)帶來(lái)一系列問(wèn)題:

  • 用戶體驗(yàn)比較差,最開(kāi)始是白屏耍攘,然后突然又渲染出現(xiàn)內(nèi)容榕栏。
  • 最致命的是我們判斷可見(jiàn)性是需要一個(gè)目標(biāo)來(lái)觀察的畔勤,如果什么不都渲染,我們就無(wú)從觀察扒磁。

這里引入一個(gè)骨架屏的概念庆揪,我們?yōu)檎鎸?shí)的組件做一個(gè)在尺寸、樣式上非常接近真實(shí)組件的組件妨托,叫做骨架屏缸榛。

骨架屏

骨架屏的作用有:

  • 提升用戶感知體驗(yàn)
  • 保證切換的一致性
  • 提供可見(jiàn)性觀察的目標(biāo)對(duì)象

如何提升切換時(shí)的體驗(yàn)?

在真實(shí)組件開(kāi)始渲染的時(shí)候兰伤,需要一定的時(shí)間和空間内颗,時(shí)間指的是真實(shí)組件從創(chuàng)建到渲染的時(shí)間,包括請(qǐng)求接口医清、請(qǐng)求資源和渲染的時(shí)間起暮,空間指的是頁(yè)面布局中需要給真實(shí)組件留出剛好的位置,避免產(chǎn)生抖動(dòng)会烙。

這里我們可以使用 Vue.js 內(nèi)置的 transition 組件自定義骨架組件和真實(shí)組件的進(jìn)入和離開(kāi)效果,通過(guò)合理的布局和定位筒捺,減少切換時(shí)的抖動(dòng)柏腻,
通過(guò)設(shè)置過(guò)渡效果給真實(shí)組件留出一定的加載時(shí)間。

上面的問(wèn)題都有了答案之后系吭,我們很容易就可以實(shí)現(xiàn)一個(gè)通用的方案五嫂,來(lái)解決組件的懶加載問(wèn)題。

Vue組件懶加載方案介紹

項(xiàng)目Github地址: https://github.com/xunleif2e/vue-lazy-component

這個(gè)是我們基于上面的思考做的一個(gè)通用的解決方案肯尺,下面簡(jiǎn)單介紹一下特性沃缘、使用以及 API 方面的知識(shí),后面結(jié)合 5 個(gè)具體的 DEMO 來(lái)講解更高級(jí)的用法则吟。

特性

  • 支持 組件可見(jiàn)或即將可見(jiàn)時(shí)懶加載
  • 支持 組件延時(shí)加載
  • 支持 加載組件前展示組件骨架槐臀,提高用戶體驗(yàn)
  • 支持 懶加載組件分包異步加載

安裝和使用

npm i @xunlei/vue-lazy-component
  • 方式1 利用插件方式全局注冊(cè)
  • 方式2 局部注冊(cè)
  • 方式3 獨(dú)立版本引入,自動(dòng)全局注冊(cè)

用法

使用方式

Props

參數(shù) 說(shuō)明 類(lèi)型 可選值 默認(rèn)值
viewport 組件所在的視口氓仲,如果組件是在頁(yè)面容器內(nèi)滾動(dòng)水慨,視口就是該容器 HTMLElement true null,代表視窗
direction 視口的滾動(dòng)方向, vertical代表垂直方向敬扛,horizontal代表水平方向 String true vertical
threshold 預(yù)加載閾值, css單位 String true 0px
tagName 包裹組件的外層容器的標(biāo)簽名 String true div
timeout 等待時(shí)間晰洒,如果指定了時(shí)間,不論可見(jiàn)與否啥箭,在指定時(shí)間之后自動(dòng)加載 Number true -

Events

事件名 說(shuō)明 事件參數(shù)
before-init 模塊可見(jiàn)或延時(shí)截止導(dǎo)致準(zhǔn)備開(kāi)始加載懶加載模塊 -
init 開(kāi)始加載懶加載模塊谍珊,此時(shí)骨架組件開(kāi)始消失 -
before-enter 懶加載模塊開(kāi)始進(jìn)入 el
before-leave 骨架組件開(kāi)始離開(kāi) el
after-leave 骨架組件已經(jīng)離開(kāi) el
after-enter 懶加載模快已經(jīng)進(jìn)入 el
after-init 初始化完成 -

DEMO 1 超長(zhǎng)頁(yè)面懶加載

https://xunleif2e.github.io/vue-lazy-component/demo/dist/index.html#/large-page

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

通過(guò)上面這種簡(jiǎn)單的使用方式就可以實(shí)現(xiàn)組件即將可見(jiàn)時(shí)自動(dòng)加載急侥。

DEMO 2 延時(shí)加載

https://xunleif2e.github.io/vue-lazy-component/demo/dist/index.html#/timeout

延時(shí)加載
<vue-lazy-component :timeout="1000">
    <st-series-sohu/>
    <st-series-sohu-skeleton slot="skeleton"/>
</vue-lazy-component>

如果有時(shí)候僅僅是希望某些組件稍后渲染砌滞,而不一定要等到可見(jiàn)時(shí)炼七,可以通過(guò)這種方式。

比如我們業(yè)務(wù)中可能會(huì)有些運(yùn)營(yíng)性質(zhì)的掛件布持,就可以采取延時(shí)加載的方式豌拙。

DEMO 3 自定義過(guò)渡效果

https://xunleif2e.github.io/vue-lazy-component/demo/dist/index.html#/custom-transition

自定義過(guò)渡效果

如果覺(jué)得 Vue Lazy Component 自帶的淡入淡出的過(guò)渡效果太丑,或者需要調(diào)整淡入淡出效果的時(shí)長(zhǎng)题暖,就可以通過(guò)自定義樣式來(lái)改變過(guò)渡效果按傅。這個(gè)例子演示了另外一種過(guò)渡效果,transition 的生命周期可以參考 Vue.js 的 transition 組件的文檔胧卤。

DEMO 4 webpack 分包

https://xunleif2e.github.io/vue-lazy-component/demo/dist/index.html#/large-page-chunks

懶加載組件異步加載

DEMO1 演示了如何懶加載模塊唯绍,但其實(shí)只是推遲了模塊的渲染和模塊內(nèi)的資源的加載,如果我們需要更進(jìn)一步枝誊,連模塊本身的代碼也是懶加載况芒,就像 AMD 那樣異步按需加載,這個(gè)也是可以做到的叶撒。

這里可以利用Vue.js的異步組件绝骚,將每個(gè)真實(shí)組件都注冊(cè)成異步組件,在異步組件的工廠函數(shù)里使用 Webpack 的 AMD 版本的 require 祠够,就可以實(shí)現(xiàn)真實(shí)組件可以分成獨(dú)立的 bundle 加載压汪,脫離頁(yè)面 js 的bundle。

但是這里會(huì)有個(gè)問(wèn)題古瓤,就算模塊是可見(jiàn)時(shí)才渲染止剖,在打開(kāi)頁(yè)面的時(shí)候會(huì)發(fā)現(xiàn)模塊不可見(jiàn)之前它的 bundle 已經(jīng)加載了,這并沒(méi)有實(shí)現(xiàn)按需加載落君。

這個(gè)例子演示了一種做法穿香,Vue Lazy Component 可以在即將切換真實(shí)組件前通過(guò) Scoped Slots 傳遞一個(gè) loading 屬性給真實(shí)組件,真實(shí)組件只要是根據(jù)這個(gè) loading 來(lái)?xiàng)l件渲染就可以避免非按需加載绎速,這個(gè)和 Vue.js 對(duì)組件的解析機(jī)制有關(guān)皮获,例子里有相應(yīng)的的代碼,有興趣的同學(xué)可以深入研究下朝氓。

DEMO 5 特定視口內(nèi)懶加載

https://xunleif2e.github.io/vue-lazy-component/demo/dist/index.html#/specific-viewport

IM場(chǎng)景

在某些場(chǎng)景下魔市,我們要解決滾動(dòng)容器內(nèi)的組件懶加載,這個(gè)時(shí)候可見(jiàn)性是相對(duì)與這個(gè)視口來(lái)的赵哲,這個(gè)例子演示了如何指定聊天窗口作為觀察的視口待德。

這里吐槽下Vue.js的 $parent 、$refs 的設(shè)計(jì)枫夺,它們都不是響應(yīng)式的将宪,如果需要?jiǎng)討B(tài)獲取這些組件引用上的 $el ,必須要等到 mounted 事件發(fā)生之后,所以例子的代碼稍微有一點(diǎn)繁瑣较坛。

應(yīng)用效果

首先 Vue Lazy Component 的設(shè)計(jì)雖然是說(shuō)組件級(jí)的印蔗,其實(shí)它的粒度可大可小,大的比如頁(yè)面不同的區(qū)域丑勤,小的就像 DEMO5 里的只是一個(gè)用戶頭像华嘹,所以適用性非常強(qiáng),只要有懶加載需求的場(chǎng)景基本都可以采用法竞。

另外耙厚,在終端方面,不僅可以兼容PC端的項(xiàng)目岔霸,在移動(dòng)端也是可以使用的薛躬,當(dāng)然,需要解決 IntersectionObserver API 的兼容性問(wèn)題呆细,在項(xiàng)目 Readme 里提到了 w3c 的 polyfill 的地址型宝。

應(yīng)用業(yè)務(wù)

我們目前應(yīng)用在迅雷的兩個(gè)項(xiàng)目中,一個(gè)是 PC 迅雷的首頁(yè)項(xiàng)目絮爷,一個(gè)是 PC 迅雷的組隊(duì)加速項(xiàng)目趴酣,后期預(yù)計(jì)會(huì)推廣到更多的業(yè)務(wù)中去。

迅雷前端項(xiàng)目

優(yōu)化后請(qǐng)求瀑布圖

我們?cè)賮?lái)看看開(kāi)始那個(gè)頁(yè)面的情況略水,在使用了 組件懶加載技術(shù)后价卤,請(qǐng)求數(shù)變成了只有 31 個(gè),瀑布圖變得比較短了渊涝。

請(qǐng)求數(shù)變?yōu)?1個(gè)

數(shù)據(jù)對(duì)比

我們把前后的數(shù)據(jù)進(jìn)行一個(gè)對(duì)比:

  • 請(qǐng)求數(shù)變成之前的 1 / 5,優(yōu)化效果比較明顯
  • 請(qǐng)求大小相比之前降低不太明顯
  • load 時(shí)長(zhǎng)也同樣不太明顯

分析主要是有較多圖片未按照使用尺寸裁剪和壓縮床嫌,導(dǎo)致請(qǐng)求大小較大跨释,同時(shí)造成了 load 時(shí)長(zhǎng)的拖長(zhǎng)。

后續(xù)性能優(yōu)化方向

后續(xù)我們會(huì)繼續(xù)來(lái)優(yōu)化這個(gè)頁(yè)面厌处,主要的方向有兩個(gè):

圖片尺寸適配和壓縮

通過(guò)圖片的裁剪和壓縮鳖谈,解決請(qǐng)求資源大小較大子資源加載時(shí)間較長(zhǎng)導(dǎo)致 load 時(shí)間拖長(zhǎng)的問(wèn)題

預(yù)渲染

采用預(yù)渲染插件將頁(yè)面的主要 css 、js 進(jìn)行內(nèi)聯(lián)阔涉,將骨架架屏通過(guò)預(yù)渲染生成出來(lái)缆娃,這樣可以避免 SPA 首屏可見(jiàn)關(guān)鍵路徑較長(zhǎng)的問(wèn)題,在頁(yè)面解析完 dom 樹(shù)以后即可保證首屏可見(jiàn)瑰排。

懶加載方案 ROADMAP

Vue Lazy Component 懶加載方案還有些地方做得還不夠好贯要,計(jì)劃在后期的幾個(gè)小版本里支持以下的特性:

  • SSR 支持 v1.1.0
  • UI單元測(cè)試 v1.2.0
  • 減少性能開(kāi)銷(xiāo) v1.3.0
    • 重繪
    • FPS

后記

這篇文章分享了從遇到業(yè)務(wù)實(shí)際性能問(wèn)題,到分析椭住、解決并梳理出通用的解決方案的過(guò)程崇渗,重點(diǎn)其實(shí)不是最終的實(shí)現(xiàn)代碼實(shí)現(xiàn),而是解決問(wèn)題的角度和過(guò)程。

最后歡迎大家通過(guò)提交 issue 或者 PR 的方式參與貢獻(xiàn)宅广,項(xiàng)目 Github 地址: https://github.com/xunleif2e/vue-lazy-component 葫掉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跟狱,隨后出現(xiàn)的幾起案子俭厚,更是在濱河造成了極大的恐慌,老刑警劉巖驶臊,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挪挤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡资铡,警方通過(guò)查閱死者的電腦和手機(jī)电禀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)笤休,“玉大人尖飞,你說(shuō)我怎么就攤上這事〉暄牛” “怎么了政基?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)闹啦。 經(jīng)常有香客問(wèn)我沮明,道長(zhǎng),這世上最難降的妖魔是什么窍奋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任荐健,我火速辦了婚禮,結(jié)果婚禮上琳袄,老公的妹妹穿的比我還像新娘江场。我一直安慰自己,他們只是感情好窖逗,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布址否。 她就那樣靜靜地躺著,像睡著了一般碎紊。 火紅的嫁衣襯著肌膚如雪佑附。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,475評(píng)論 1 312
  • 那天仗考,我揣著相機(jī)與錄音音同,去河邊找鬼。 笑死痴鳄,一個(gè)胖子當(dāng)著我的面吹牛瘟斜,可吹牛的內(nèi)容都是我干的缸夹。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼螺句,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼虽惭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蛇尚,我...
    開(kāi)封第一講書(shū)人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芽唇,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后取劫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體匆笤,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年谱邪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了炮捧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惦银,死狀恐怖咆课,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扯俱,我是刑警寧澤书蚪,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站迅栅,受9級(jí)特大地震影響殊校,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜读存,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一为流、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧让簿,春花似錦艺谆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)琅催。三九已至居凶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間藤抡,已是汗流浹背侠碧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留付枫,地道東北人急凰。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像欣福,于是被迫代替她去往敵國(guó)和親替饿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子语泽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容