探索react native首屏渲染最佳實務

來源:互聯網
上載者:User

標籤:

1.前言

       react native給了我們使用javascript開發原生app的能力,在使用react native完成興趣部落安卓端發現tab改造後,我們開始對由react native實現的介面進行持續最佳化。目標只有一個,在享受react native帶來的新特性的同時,在體驗上無限逼近原生實現。作為一名前端開發,本文會從前端角度,探索react native首屏渲染最佳實務。

2.首屏耗時計算方法

2.1我們關注的耗時

      最佳化首屏渲染耗時,需要先定義首屏耗時的衡量方法。將react native整合至原生app中時,可以將首屏耗時定義為如下

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

  其中,react native上下文初始化耗時為一個固定開銷,通過將初始化過程提前至app啟動後非同步進行,在安卓端,這一耗時已經可以降低到約70ms。本文關注的是首屏視圖渲染耗時,文中最佳化探索是在安卓端react native結合版app中進行,但其思路和方法,可以複用至iOS端。

2.2渲染耗時衡量方法

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

  對應上述生命週期方法,我們在初始階段首先渲染loading視圖,並開始拉取資料。擷取資料後,通過改變狀態(state),觸發視圖的再次渲染,在螢幕繪製出視圖。

  

  結合上述分析,我們將首屏視圖渲染耗時定義為如下

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

3.開啟渲染最佳化探索之路

3.1輸出當前渲染耗時

  既然已經明確了視圖渲染耗時的計算方法,那麼就可以打時間點日誌輸出這一耗時看看。這裡查看耗時有兩種方法。

  a)     使用偵錯模式,通過console.log,在chrome調試工具中輸出時間差值

  b)     封裝native層日誌方法封裝給react native層調用,將日誌輸出到app記錄檔中。

  推薦使用第二種方法,採用這種方法,可以以release模式運行app,輸出結果與我們普通的安裝運行app表現一致。

  這時我們的耗時輸出是怎樣的呢?查看日誌得知,在wifi環境下,榮耀4X手機上,渲染耗時為約700ms,加上react native上下文初始化時間約70ms耗時,整個介面的耗時需要約770ms。

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

3.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。

3.3接管輪播圖

       加入緩衝最佳化後,我們將渲染耗時降低至400ms,但是仍與原生實現中耗時有較大差距。此時的最佳化進入了深水區,經過梳理介面視圖,可以將視圖劃分為上部輪播圖、中間部落列表和下部熱門文章三個模組,逐一最佳化。

  在最初的版本中,我們是這麼實現輪播組件的:在請求的返回結果中,取出輪播圖集合,依次產生全部圖片視圖,並添加至輪播圖[內容] 檢視中。最後監聽[內容] 檢視的對應事件,設定當前顯示的圖片視圖。

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

  從上述過程可以看出一個明顯的問題:對首屏來講,輪播圖容器內,非可見狀態的其餘圖片視圖的渲染是沒有意義的。

  既然已經找到了問題,最佳化是思路也就很自然:在從請求結果中擷取輪播圖集合後,不是產生全部圖片視圖,而是僅產生一份,將這一個圖片視圖添加至容器內,並為他設定當前圖片的url地址。當容器內滑動事件發生時,不再是切換不同視圖的可見狀態,而是複用這一個圖片視圖,切換視圖圖片的url地址,其流程如所示。 

  採用這一方案,完全去除了非可見狀態的圖片視圖的渲染耗時,同時,通過複用視圖,也降低了對記憶體的佔用。這一方案在理論上將渲染耗時和記憶體佔用做到了最優,然而在實際運用中,會有一個體驗的問題:當滑動發生時,才載入下一張圖片並重新整理圖片視圖,會導致容器內滑動事件的卡頓,使得滑動有阻滯感。因此我們最終採用了一種折衷方案:當輪播圖集合超過三張時,在容器內加入當前圖片視圖,同時提前加入當前圖片的上一張和下一張圖片視圖。容器內滑動事件發生時,複用這三個視圖,來設定這次事件對應的當前圖片、上一張和下一站圖片視圖。

  通過日誌輸出最佳化前後的耗時資料,採用這一方案時,在渲染耗時上降低了約30ms,此時我們的首屏渲染耗時下降到了約370ms。

3.4為列表加特效

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

  最初的版本中,我們從請求結果資料中擷取子類別下部落集合,以ScrollView為容器,依次建立清單項目視圖並添加至容器中。這樣,當所有清單項目渲染完成,該子類的部落列表才渲染完成,最初的實現示意如下所示。

  通過上述過程可以發現,在部落列表渲染過程中,等待非可視地區的清單項目的圖片下載再渲染該視圖,對於首屏是沒有意義的。

  結合對輪播圖組件的最佳化經驗,我們嘗試消極式載入清單項目視圖。理想的狀態是在首屏僅渲染可見的幾個清單項目,非可見地區的清單項目,延遲渲染,流程如下所示

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

  繼續思考,雖然我們不能預知在首屏應該幾個清單項目,但是利用react native 提供的API方法,我們可以擷取當前視圖相對於容器的座標位置。

static measureLayout(relativeToNativeNode: number, onSuccess: MeasureLayoutOnSuccessCallback, onFail: () => void)

  利用measureLayout方法,在視圖完成布局,觸發其onLayout事件回調後,可以通過視圖的measureLayout方法,傳入容器節點,從而獲得當前視圖在容器中的相對位置。利用這一特性,結合我們的視圖特點——清單項目視圖的尺寸是確定的,可以在初始時,將所有清單項目視圖渲染為這一特定尺寸的空視圖,待所有空視圖渲染完成後,擷取視圖在容器中相對位置,僅將可視地區內的清單項目用真實視圖重新渲染。同時監聽列表容器的滾動事件,在滾動事件回調中,計算視圖在容器中的相對位置,當視圖進入了可視地區時,再用真實視圖替換空視圖,上述流程如下所示

  通過上述方案,我們可以實現清單項目的延遲渲染特性,通過日誌輸出耗時時間,可以發現通過這一最佳化,渲染最佳化又可降低約30ms。

  上述方案中,螢幕在首次渲染空白視圖和再次渲染首屏可見清單項目視圖之間,有短暫閃動的感覺,為解決這一體驗問題,可使用佔位圖方案替代空視圖方案,即首先都用靜態資源佔位圖來渲染清單項目icon圖,並消極式載入非可見清單項目icon圖,其方案如下所示

  實現效果如下,首次渲染時均用佔位圖,首屏地區內的清單項目icon視圖依次下載並重新渲染。在採用這樣實現後,解決了空視圖帶來的螢幕閃動問題,快速的完成初次渲染,實現了icon圖片的消極式載入,渲染耗時稍有增加,相比空視圖方案耗時增加約10ms,此時我們的首屏渲染耗時下降到了約350ms。

3.5熱門帖模組的歸宿

       發現tab視圖下部還有一個模組,如下所示,是幾條熱門文章的展示地區。對於首屏內容來說,這一模組整個都屬於非可視地區,因此我們需要設法將這一塊的耗時從首屏耗時中拿掉。

  有了上面部落列表的最佳化經驗,我們已經知道,在布局事件完成後,可以擷取視圖在容器內的相對位置,對於滾動視圖如ScrollView,我們可以監聽他的滾動事件,來判斷視圖距離可視地區的位置以決定是否渲染該視圖,為了最佳化體驗,我們再給視圖的渲染設定一個提前閾值aheadDistance,當視圖距離可視地區還有aheadDistance單位時,提前渲染該視圖,這一方法如所示

  採用這一方案後,已經可以將熱門文章模組的渲染耗時從首屏耗時中去掉了,此時通過日誌輸出耗時分析,首屏渲染耗時下降到了約270ms。

  那麼這塊的最佳化工作完成了嗎?沒有,在最佳化做完,回顧體驗時,我們發現了這裡仍存在體驗問題。這一體驗問題與發現tab的視圖結構有關。在發現tab中,熱門文章模組,距可視地區底部的距離不遠。因為本身距離可視地區底部很接近,所以ahead Distance的設定並沒有起到預想的效果,導致發現tab介面向下滑動時,因為即時去渲染熱門文章模組,使得滑動不流暢,有阻滯感。

  為瞭解決這一體驗問題,繼續對熱門文章模組做了定製的最佳化,最終對熱門文章模組做到了非同步渲染。在首屏渲染時,還是將熱門文章渲染為空白視圖,然後將ahead Distance調大,使得空視圖落在ahead Distance地區內,接著使用setTimeout技巧,非同步去計算該空視圖在容器中位置,並將熱門文章真實視圖渲染出來。

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

4.總結

       react native架構給了我們新的能力,使得我們可以用javascript開發原生app。當我們走入react native應用的深水區,開始對他進行各方面細緻的最佳化時,我們在原來web端積累的最佳實務依然有效,諸如緩衝的使用、元素複用、消極式載入、非同步載入等方法依然會起到很好的效果。

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

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

 

探索react native首屏渲染最佳實務

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.