探索 React Native 首屏渲染最佳實踐

react native給了咱們使用javascript開發原生app的能力,在使用react native完成興趣部落安卓端發現tab改造後,咱們開始對由react native實現的界面進行持續優化。目標只有一個,在享受react native帶來的新特性的同時,在體驗上無限逼近原生實現。javascript

做爲一名前端開發,本文會從前端角度,探索react native首屏渲染最佳實踐。前端

1. 首屏耗時計算方法

1.1 咱們關注的耗時

優化首屏渲染耗時,須要先定義首屏耗時的衡量方法。將react native集成至原生app中時,能夠將首屏耗時定義爲以下java

$$首屏耗時=react native上下文初始化耗時+首屏視圖渲染耗時$$react

其中,react native上下文初始化耗時爲一個固定開銷,經過將初始化過程提早至app啓動後異步進行,在安卓端,這一耗時已經能夠下降到約70ms。本文關注的是首屏視圖渲染耗時,文中優化探索是在安卓端react native結合版app中進行,但其思路和方法,能夠複用至iOS端。web

1.2 渲染耗時衡量方法

關注首屏視圖渲染耗時,須要理解react框架視圖渲染流程,相應的須要瞭解其生命週期方法。下圖是一張react組件完整的聲明週期圖,從圖中能夠看出,上方虛線框內爲生命週期第一階段,這個階段完成初始化,並第一次渲染組件。左下角虛線框爲組件運行和交互階段,這裏可能會再次渲染組件。右下角虛線框爲第三階段,組件這一階段卸載消亡。chrome

對應上述生命週期方法,咱們在初始階段首先渲染loading視圖,並開始拉取數據。獲取數據後,經過改變狀態(state),觸發視圖的再次渲染,在屏幕繪製出視圖。緩存

結合上述分析,咱們將首屏視圖渲染耗時定義爲以下網絡

$$首屏視圖渲染耗時=compontDidUpdate結束時間 – compontWillMount開始時間$$app

2. 開啓渲染優化探索之路

2.1 輸出當前渲染耗時

既然已經明確了視圖渲染耗時的計算方法,那麼就能夠打時間點日誌輸出這一耗時看看。這裏查看耗時有兩種方法。
使用調試模式,經過console.log,在chrome調試工具中輸出時間差值。框架

封裝native層日誌方法封裝給react native層調用,將日誌輸出到app日誌文件中。

推薦使用第二種方法,採用這種方法,能夠以release模式運行app,輸出結果與咱們普通的安裝運行app表現一致。
這時咱們的耗時輸出是怎樣的呢?查看日誌得知,在wifi環境下,榮耀4X手機上,渲染耗時爲約700ms,加上react native上下文初始化時間約70ms耗時,整個界面的耗時須要約770ms。

那麼原生app的實現中,發現tab渲染耗時是多少呢?經過打點發現,安卓端原生app實現中,發現tab從onCreateViewEx開始至onResume結束,耗時約100ms。

2.2 來吧,加上緩存

看到上述數據時,個人心裏是有點崩潰的,說好的高性能呢?可是我並不慌,根據個人前端開發經驗,我知道有一個大招尚未用上,那就是使用緩存數據。

react native爲咱們提供了AsyncStorage模塊,AsyncStorage是一個簡單的、異步的、持久化的Key-Value存儲系統,它對於App來講是全局性的。相對於以前咱們使用LocalStorage存儲數據,在react native端,咱們可使用AsyncStorage做爲數據存儲解決方案。

在使用緩存以前,咱們的視圖渲染流程以下所示

上述流程中,每次渲染界面視圖前,都須要等待網絡請求。這裏咱們將請求數據緩存起來,渲染界面視圖時,首先使用緩存數據渲染視圖,同時發起網絡請求,數據返回時再以新的數據渲染一遍。使用緩存以後的渲染流程以下圖所示

上述流程中,當有緩存數據時,咱們會快速拿到緩存數據渲染出界面視圖。而後在網絡請求返回數據時,再次觸發render方法,以新數據再次渲染視圖。

這裏爲了提高性能,避免多餘的觸發render方法,能夠在shouldComponentUpdate方法中判斷cache data和response data差別,僅當兩份數據不一致時纔再次觸發render方法。同時,得益於react 框架的虛擬dom特性,在網絡請求的數據返回再次觸發render方法後,react native會計算dom diff並以此爲依據來斷定視圖更新範圍。

此時經過日誌數據能夠看到,在有緩存場景下,咱們獲取緩存時間耗時約在40ms,此時咱們的渲染耗時降低至400ms。

2.3 接管輪播圖

加入緩存優化後,咱們將渲染耗時下降至400ms,可是仍與原生實現中耗時有較大差距。此時的優化進入了深水區,通過梳理界面視圖,能夠將視圖劃分爲上部輪播圖、中間部落列表和下部熱門帖子三個模塊,逐一優化。

在最初的版本中,咱們是這麼實現輪播組件的:在請求的返回結果中,取出輪播圖集合,依次生成所有圖片視圖,並添加至輪播圖容器視圖中。最後監聽容器視圖的對應事件,設置當前顯示的圖片視圖。

以下圖所示,當結果數據中共有五張輪播圖時,會有五個圖片視圖被添加至輪播圖容器中。初始時僅首張圖片視圖可見,容器內滑動事件發生時,圖片視圖可見狀態隨之改變。

從上述過程能夠看出一個明顯的問題:對首屏來說,輪播圖容器內,非可見狀態的其他圖片視圖的渲染是沒有意義的。
既然已經找到了問題,優化是思路也就很天然:在從請求結果中獲取輪播圖集合後,不是生成所有圖片視圖,而是僅生成一份,將這一個圖片視圖添加至容器內,併爲他設置當前圖片的url地址。當容器內滑動事件發生時,再也不是切換不一樣視圖的可見狀態,而是複用這一個圖片視圖,切換視圖圖片的url地址,其流程以下圖所示。

採用這一方案,徹底去除了非可見狀態的圖片視圖的渲染耗時,同時,經過複用視圖,也下降了對內存的佔用。這一方案在理論上將渲染耗時和內存佔用作到了最優,然而在實際運用中,會有一個體驗的問題:當滑動發生時,才加載下一張圖片並刷新圖片視圖,會致使容器內滑動事件的卡頓,使得滑動有阻滯感。

所以咱們最終採用了一種折衷方案:當輪播圖集合超過三張時,在容器內加入當前圖片視圖,同時提早加入當前圖片的上一張和下一張圖片視圖。容器內滑動事件發生時,複用這三個視圖,來設置此次事件對應的當前圖片、上一張和下一站圖片視圖。

經過日誌輸出優化先後的耗時數據,採用這一方案時,在渲染耗時上下降了約30ms,此時咱們的首屏渲染耗時降低到了約370ms。

2.4 爲列表加特效

在發現tab界面中部,是子類別的部落列表。子類別數量一般是兩個,每一個子類別下通常有七至八個列表項,每一個列表項由部落圖標和部落名稱組成。

最初的版本中,咱們從請求結果數據中獲取子類別下部落集合,以ScrollView爲容器,依次建立列表項視圖並添加至容器中。這樣,當全部列表項渲染完成,該子類的部落列表才渲染完成,最初的實現示意以下所示。

經過上述過程能夠發現,在部落列表渲染過程當中,等待非可視區域的列表項的圖片下載再渲染該視圖,對於首屏是沒有意義的。

結合對輪播圖組件的優化經驗,咱們嘗試延遲加載列表項視圖。理想的狀態是在首屏僅渲染可見的幾個列表項,非可見區域的列表項,延遲渲染,流程以下所示

很快咱們遇到了問題,在輪播圖組件中,首屏僅顯示第一張圖片。可是在橫向部落列表中,首屏應該渲染幾個列表項呢?答案是咱們沒法提早肯定,首屏可見的列表項,只有在渲染時,根據當前列表項在橫向列表中的相對位置,才能計算出該項是否可見。

繼續思考,雖然咱們不能預知在首屏應該幾個列表項,可是利用react native 提供的API方法,咱們能夠獲取當前視圖相對於容器的座標位置。

利用measureLayout方法,在視圖完成佈局,觸發其onLayout事件回調後,能夠經過視圖的measureLayout方法,傳入容器節點,從而得到當前視圖在容器中的相對位置。利用這一特性,結合咱們的視圖特色——列表項視圖的尺寸是肯定的,能夠在初始時,將全部列表項視圖渲染爲這一特定尺寸的空視圖,待全部空視圖渲染完成後,獲取視圖在容器中相對位置,僅將可視區域內的列表項用真實視圖從新渲染。

同時監聽列表容器的滾動事件,在滾動事件回調中,計算視圖在容器中的相對位置,當視圖進入了可視區域時,再用真實視圖替換空視圖,上述流程以下所示

經過上述方案,咱們能夠實現列表項的延遲渲染特性,經過日誌輸出耗時時間,能夠發現經過這一優化,渲染優化又可下降約30ms。

上述方案中,屏幕在首次渲染空白視圖和再次渲染首屏可見列表項視圖之間,有短暫閃動的感受,爲解決這一體驗問題,可以使用佔位圖方案替代空視圖方案,即首先都用靜態資源佔位圖來渲染列表項icon圖,並延遲加載非可見列表項icon圖,其方案以下所示


實現效果以下,首次渲染時均用佔位圖,首屏區域內的列表項icon視圖依次下載並從新渲染。在採用這樣實現後,解決了空視圖帶來的屏幕閃動問題,快速的完成初次渲染,實現了icon圖片的延遲加載,渲染耗時稍有增長,相比空視圖方案耗時增長約10ms,此時咱們的首屏渲染耗時降低到了約350ms。

2.5熱門帖模塊的歸宿

發現tab視圖下部還有一個模塊,以下所示,是幾條熱門帖子的展現區域。對於首屏內容來講,這一模塊整個都屬於非可視區域,所以咱們須要設法將這一塊的耗時從首屏耗時中拿掉。

有了上面部落列表的優化經驗,咱們已經知道,在佈局事件完成後,能夠獲取視圖在容器內的相對位置,對於滾動視圖如ScrollView,咱們能夠監聽他的滾動事件,來判斷視圖距離可視區域的位置以決定是否渲染該視圖。

爲了優化體驗,咱們再給視圖的渲染設置一個提早閾值aheadDistance,當視圖距離可視區域還有aheadDistance單位時,提早渲染該視圖,這一方法以下圖所示

採用這一方案後,已經能夠將熱門帖子模塊的渲染耗時從首屏耗時中去掉了,此時經過日誌輸出耗時分析,首屏渲染耗時降低到了約270ms。

那麼這塊的優化工做完成了嗎?沒有,在優化作完,回顧體驗時,咱們發現了這裏仍存在體驗問題。這一體驗問題與發現tab的視圖結構有關。在發現tab中,熱門帖子模塊,距可視區域底部的距離不遠。由於自己距離可視區域底部很接近,因此ahead Distance的設置並無起到預想的效果,致使發現tab界面向下滑動時,由於實時去渲染熱門帖子模塊,使得滑動不流暢,有阻滯感。

爲了解決這一體驗問題,繼續對熱門帖子模塊作了定製的優化,最終對熱門帖子模塊作到了異步渲染。在首屏渲染時,仍是將熱門帖子渲染爲空視圖,而後將ahead Distance調大,使得空視圖落在ahead Distance區域內,接着使用setTimeout技巧,異步的去計算該空視圖在容器中位置,並將熱門帖子真實視圖渲染出來。

經過上述方案,將延遲渲染替換爲異步渲染,在不增長渲染耗時的基礎上,同時解決了滑動不流暢的體驗問題,最終將首屏渲染耗時定格在約270ms。

3. 總結

react native框架給了咱們新的能力,使得咱們能夠用javascript開發原生app。當咱們走入react native應用的深水區,開始對他進行各方面細緻的優化時,咱們在原來web端積累的最佳實踐依然有效,諸如緩存的使用、元素複用、延遲加載、異步加載等方法依然會起到很好的效果。

與此同時,對渲染耗時的優化也要兼顧使用的體驗,咱們應該追求的,不只僅是快。而是在快的基礎上,也有很好的使用體驗。

優化探索到最後,咱們將首屏渲染耗時定格在約270ms,加上react native初始化耗時約70ms,咱們的首屏總耗時仍需約340ms,相比原生實現仍然存在差距。原生實現的渲染速度也是通過了層層的優化,而咱們對react native最佳實踐的探索也沒有結束,歡迎你們指導、探討。


文 / 騰訊   龔麒騰訊優測是專業的移動雲測試平臺,爲應用、遊戲、H5混合應用的研發團隊提供產品質量檢測與問題解決服務。不只在線上平臺提供自動化兼容性測試、雲手機遠程租用與調試、漏洞分析、自動化測試工具Xtest等多種質量檢測工具,更爲VIP客戶配備了專家團隊提供定製化綜合測試解決方案。

相關文章
相關標籤/搜索