React Native 效能優化 (官網指南搬運)
最近在寫React-Native 趁著這兩天需求差不多完成了,實踐了一些優化項。
記錄於此
Life sucks
Performance
檢視效能
開啟開發者選單(搖晃手機開啟):point_right: 開啟 Show Perf Monitor
可以看到下圖顯示框
UI 和 JS 的幀數都穩定保持在60 為最優情況。
JS 的單執行緒
所有的事件處理,API請求,等操作都在這個執行緒上,在 this.setState
大量資料時,狀態的變動會導致 re-render ,這期間所有由JavaScript 控制的動畫都會出現卡頓掉幀
比如在切換路由時,幀數會有明顯抖動。此時如果有一些在 componentDidMount
執行的操作就會使得路由過渡動畫非常卡頓。(後面會介紹一些可以嘗試的解決方案
開發環境效能比生產環境差
開發環境下框架會有很多別的操作比如warning error 的輸出,型別檢測等等。
如果要測試效能,最好在 release
包測試。這樣更加精準。
生產環境移除console.*
開發時,會有很多 console.*
指令來幫助除錯。並且一些依賴庫也會有 console.*
這些語句對JavaScript 執行緒來說是一個極大的消耗。可以通過Babel 在生產環境中移除掉 console.*
。
-
安裝外掛
npm i babel-plugin-transform-remove-console --save-dev
-
配置
.babelrc
檔案{ "env": { "production": { "plugins": ["transform-remove-console"] } } }
處理大量資料列表時使用 <FlatList />
FlatList
元件更加適合來展示長列表,並且指定合適的 getItemLayout
方法, getItemLayout
會跳過渲染Item 時的佈局計算,直接使用給定的配置(詳情檢視連結:point_up:)
依賴懶載入
在框架執行編寫好的業務程式碼前,需要把在記憶體中載入並解析程式碼,程式碼量越大這個過程就更耗時,導致首屏渲染速度過慢。而且往往會出現一些頁面或者元件根本不會被使用者訪問到。這時可以通過懶載入來優化。
官網 有給出例子
VeryExpensive.js
import React, { Component } from 'react'; import { Text } from 'react-native'; // ... import some very expensive modules // You may want to log at the file level to verify when this is happening console.log('VeryExpensive component loaded'); export default class VeryExpensive extends Component { // lots and lots of code render() { return <Text>Very Expensive Component</Text>; } }
Optimized.js
import React, { Component } from 'react'; import { TouchableOpacity, View, Text } from 'react-native'; let VeryExpensive = null; //定義變數 export default class Optimized extends Component { state = { needsExpensive: false }; // 定義內部狀態來控制組件是否需要載入 didPress = () => { // 在觸發需要載入元件的事件時 if (VeryExpensive == null) { // 不重複引用 VeryExpensive = require('./VeryExpensive').default;// 把元件的引用賦給定義好的變數 } this.setState(() => ({ needsExpensive: true, // 更改控制的狀態,觸發元件re-render })); }; render() { return ( <View style={{ marginTop: 20 }}> <TouchableOpacity onPress={this.didPress}> <Text>Load</Text> </TouchableOpacity> {this.state.needsExpensive ? <VeryExpensive /> : null} </View> ); } }
優化元件渲染次數
React 在內部 state
或者外部傳入的 props
發生改變時,會重新渲染元件。如果在短時間內有大量的元件要重新渲染就會造成嚴重的效能問題。這裡有一個可以優化的點。
- 使用
PureComponent
讓元件自己比較props
的變化來控制渲染次數,實踐下來這種可控的方式比純函式元件要靠譜。或者在Component
中使用shouldComponentUpdate
方法,通過條件判斷來控制組件的更新/重新渲染。 - 使用
PureComponent
時要注意這個元件內部是淺比較狀態,如果props
的有大量引用型別物件,則這些物件的內部變化不會被比較出來。所以在編寫程式碼時儘量避免複雜的資料結構 - 細粒度元件,拆分動態/靜態元件。需要在專案穩定並有一定規模後來統一規劃。
- 學習 immutable-js
非同步,回撥
JavaScript 單執行緒,要利用好它的 非同步 特性,和一些鉤子回撥。
比如上面提到路由切換時 componentDidMount
中的操作會導致卡頓,這裡可以使用 InteractionManager.runAfterInteractions()
將需要執行的操作放到 runAfterInteractions
的回撥中執行。
componentDidMount() { InteractionManager.runAfterInteractions(() => { // your actions }) }
需要注意的是 InteractionManager
是監聽所有的動畫/互動 完成之後才會觸發 runAfterInteractions
中的回撥,如果專案中有一些長時間動畫或者互動,可能會出現長時間等待。所以 由於 InteractionManager
的不可控性,使用的時候要根據實際情況調整。
在react-native 中的一些動畫反饋,比如 TouchableOpacity
在觸控時會響應 onPress
並且 自身的透明度會發生變化,這個過程中如果 onPress
中有複雜的操作,很可能會導致元件的透明反饋卡頓,這時可以將 onPress
中的操作包裹在 requestAnimationFrame
中。這裡給出一個我的實踐(利用styled-component)
import styled from 'styled-components' export const TouchableOpacity = styled.TouchableOpacity.attrs({ onPress: props => () => { requestAnimationFrame(() => { props.onPressAsync && props.onPressAsync() }, 0) } })``
這裡把 onPress
改成在 requestAnimationFrame
的回撥中執行 onPressAsync
傳入的操作。
同理,還在 FlatList
的 onReachEnd
實踐了這個操作,來避免iOS 中滾動回彈時執行操作的卡頓。
以上,記錄了近期寫React-Native 的一些實踐過的優化項。
最後
路漫漫其修遠兮,吾將上下而求索
May love & peace be with you