React Native 痛點解析之效能調優
自從React Native出世,雖然官方一直儘可能的優化其效能,為了能讓其媲美原生App的速度,但是現實感覺有點不盡人意。接下來介紹下實踐中遇到的一些效能問題以及優化方案。以下對效能引數的依據是來自於React Native自帶的FPS Monitor.
1. Navigator頁面切換動畫優化
場景:在Navigator還沒出來時,導航器是由NavigatorIOS來實現的,當時覺得頁面切換動畫很流暢,但是一旦用Navigator後,發現定義的切換動畫會使JS執行緒出現嚴重的掉幀(卡頓現象)。
原因:NavigatorIOS的切換動畫是跑在UI主執行緒上,而不是JS執行緒上的,所以不受JS執行緒上的掉幀影響。當然官方還是推薦使用Navigator,其原因如下:
-
Navigator擴充套件性的API設計使得它完全可以通過js定製,而NavigatorIOS則無js層面的定製;
-
Navigator使用JavaScript編寫,iOS和Android都可以使用,而NavigatorIOS只能在IOS上;
-
Navigator優化後的動畫效果還算不錯,而且官方還在不斷改進中,當然這個動畫比不上NavigatorIOS那麼順滑。但NavigatorIOS並不在FaceBook的應用中使用,也不是其主導開發,而是開源社群主導開發。所以可能坑多又沒人給出填坑的解決方法。
所以我們選擇導航的時候儘量選擇Navigator吧。
優化切換動畫卡頓的問題:
-
使用API InteractionManager,它的作用就是可以使本來JS的一些操作在動畫完成之後執行,這樣就可確保動畫的流程性。當然這是在延遲執行為代價上來獲得幀數的提高。
-
使用LayoutAnimation API(一次性動畫),在對動畫中途無取消要求或者其他中途回撥要求的(比如區域性元件特定顯示隱藏動畫等),則可以使用這個方案。我們可以在呼叫setState之前,呼叫LayoutAnimation方法。程式碼如下:
2. 資料型別的優化
場景:基本上每個頁面都需要載入和渲染資料,如果頁面列表資料結構複雜,有時重新整理資料時state中的未必有修改,但是遇到這樣的語句this.setState({data:samedata}) ,介面卻被重新render.
原因:這是react-native的生命週期,當你呼叫setState時,總是會觸發render的方法。
優化資料問題:可以使用shouldComponentUpdate生命週期方法,此方法作用是在props或 者state改變且接收到新的值時,則在要render方法之前呼叫。此方法在初始化渲染的時候不會呼叫,在使用forceUpdate方法的時候也不會。所以在這個方法中我們可以增加些判斷規則來避免當state或者props沒有改變時所造成的重新render.
但僅僅做這層判斷是不夠的,如果是一個列表的物件,例如下面的例子:
這裡即使使用了shouldComponentUpdate中的判斷,但卻一直返回true,導致還會執行render。所以必須對物件所有的鍵值進行進行比較才能確認是否相等。這裡推薦使用facebook自家的immutablejs。一個不可變資料型別的庫。使用後可以直接使用一下的寫法達到我們之前的目的(即使是物件都可以完美的做比較)。修改後程式碼如下:
immutablejs其他的具體用法請見: http://facebook.github.io/immutable-js/
3. 資料載入的優化
場景:在首屏頁面載入時,載入前6分鐘的資料分6頁顯示,並需保持當前選擇頁的時間的前6分鐘,如果按照此場景開發所遇到問題是:首頁載入時間太長,載入新資料時頁面顯示載入使用者體驗不順暢。
原因:首頁請求資料量過大,導致首屏頁面載入很慢;後臺資料更新時導致使用者體驗不順暢。
優化問題:減少首屏載入的資料,實現資料懶載入,其先載入3頁的資料量,然後在滑動的時候後臺去取後面的資料(例如繫結到Slider元件的onMomentumScrollEnd事件中,每次取3條資料),最後每次保持6分鐘的資料在元件中,其他資料則可放到localstorage中作為快取。這樣就可以減少首屏載入事件和提高使用者體驗。載入資料的滾動列表示例程式碼如下:
初始化(定義資料data):
滾動列表的事件:分為左滑每次到3的倍數頁面取當前取過的資料的前3分鐘的歷史資料;右滑則取之後的時間。
4. 元件響應速度的優化
場景:一個頁面包含多個類別的列表,由於列表都比較長,所以需要增加摺疊功能並增加摺疊動畫,摺疊按鈕使用的是TouchableHighlight元件。問題是當我點選摺疊或者展開按鈕時出現延遲響應和動畫掉幀的問題。
原因:在TouchableHighlight元件的onPress方法中執行了setState的操作,由於列表的物件相對來說比較複雜需要大量計算的工作,所以導致了延遲響應和JS執行緒的掉幀。
優化問題:使用requestAnimationFrame(fn)在下一幀就立即執行回撥,這樣就可以非同步來提高元件的響應速度。而摺疊動畫則可以使用LayoutAnimation一次性動畫來完成,保證其流暢性。而對於某些狀態更新,setNativeProps方法可以讓我們直接修改原生檢視元件的屬性,而不用通過setState來重新渲染結構,這樣能使整個元件響應速度變快。
還有要提醒的是儘量優化元件的View結構,當View的層級很深時渲染的速度也會變慢
5. 資源優化
場景:這裡說的資源包括React Native打出來的Bundle,圖片等靜態資源。RN的一股腦兒的打包方式,無疑一下子增大了Bundle包大小。還有一個頁面多多少少會包含一些圖片,特別是在一些商業APP中,圖片是對內容一種補充,能讓提高使用者體驗。為了能更快的載入圖片,可以把圖片打入包中,當然這個代價是巨大的。對APP來說,控制包的Size不管從商業方面還是開發效能方面都是一個很重要的引數。
優化問題:
-
對於Bundle過大,我們可以通過一些思路來優化它,首先是對其嘗試進行拆包,然後對拆包進行約束,使公共基礎那部分拆成一類包,使其可以按需載入本地檔案,而像業務邏輯等則拆成另一類包,使其可以按需載入線上檔案。
-
圖片我們可以對其轉成webp格式。webp大家應該都很熟悉了,它既支援有失真壓縮又支援無失真壓縮的圖片檔案格式。根據官方介紹其無失真壓縮後的WebP比PNG檔案少了45%的檔案大小,即使PNG檔案經過壓縮工具壓縮之後,WebP還可以減少28%的檔案大小,這可以大大提高移動端的圖片載入速度。據官網介紹在IOS平臺中,每次調整Image元件的大小,都需要重新裁剪和縮放原始圖片,重新渲染介面。這個操作開銷會非常大,尤其是大的圖片。比起直接修改尺寸,更好的方案是使用transform:[{scale}]的樣式屬性來改變尺寸。比如當你點選一個圖片,要將它放大到全屏的時候,就可以使用這個屬性。
6. 頁面載入與顯示優化
場景:某些頁面需要訪問一個或多個業務資料服務,雖然取資料是非同步,但是頁面總是會有一段較長的loading的時間。
優化問題:對於首屏所需的資料服務的訪問,使其在頁面載入階段儘早的發起資料請求,這樣有助於減少等待資料的時間。而對長時間的Loading可能會降低使用者體驗的問題,我們可以使用Fake頁來提高使用者體驗。先顯示一個Fake頁,等資料請求後並執行了相應的業務邏輯後,再替換成真正的頁面。
以上是我們在實踐中的一些優化心得,優化之路漫漫,吾將上下而求索。特別是在React Native還在成長期這個階段,優化變得尤為重要。期望React Native未來在效能上有更好的突破。