RN性能優化及事件監聽


自從React Native出世,雖然官方一直盡可能的優化其性能,為了能讓其媲美原生App的速度,但是現實感覺有點不盡人意。接下來介紹下實踐中遇到的一些性能問題以及優化方案。

一、StackNavigator頁面切換動畫優化

場景:在navigation還沒出來時,導航路由使用NavigatorIOS來實現,頁面切換是很流暢的,但是用了StackNavigator navigation發現頁面切換會使JS線程出現嚴重的掉幀(卡頓現象);

原因:
NavigatorIOS的切換動畫是跑在UI主線程上,而不是JS線程上的,所以不受JS線程上的掉幀影響;但是NavigatorIOS是但平台專用,在我們業務開發中是不能使用的;而react-navigation是跨平台的,也是官方推薦使用的;

問題:
react-navigation在從A頁面push到B頁面時,如果B頁面render存在大量可操作的視圖結構,或者componentDidMount方法里面做了耗時的操作,會發現丟幀的現在,也就是頁面頁面會卡頓2s左右,從用戶體驗上來講是非常不友好的。

react-navigation動畫是由JS線程控制的

想象一下“從右邊推入”這個場景的切換:每一幀中,新的場景從右向左移動,從屏幕右邊緣開始(不妨認為是320單位寬的的x軸偏移),最終移動到x軸偏移為0的屏幕位置。切換過程中的每一幀,JavaScript線程都需要發送一個新的x軸偏移量給主線程。

由於JS就是一單線程的,他會優先處理自頁面的dom構建,以及componentDidMount中的事件處理,這是JS線程就無法處理轉場動畫了,知道處理完自頁面釋放之后在來處理動畫,這就導致切換頁面出現卡頓現象

解決方案:
使用API InteractionManager,它的作用就是可以使本來JS的一些操作在動畫完成之后執行,這樣就可確保動畫的流程性

InteractionManager簡介

1、可以提升用戶體驗和交互效果的模塊InteractionMnager(交互管理器)

2、基本內容

使用InteractionManager可以讓一些耗時的任務在交互操作或者動畫完成之后進行執行,這樣使用可以保證我們的JavaScript的動畫效果可以平滑流暢的執行。可以大大提升用戶體驗。

在應用開發中我們可以如下進行執行任務

InteractionManager.runAfterInteractions(() => { //執行耗時的同步任務 }); 

該模塊和其他相關的調度方法對比:

  • requestAnimationFrame():執行控制動畫效果的代碼
  • setImmediate/setTimeout():設置延遲執行任務的時間,該可能會影響到正在執行的動畫
  • runAfterInteractions():延遲執行任務,該不會影響到正在執行的動畫效果

觸摸系統中的單點或者多點觸控都是交互動作,耗時任務會在這些觸摸交互動作執行完成之后或者取消以后回調runAfterInteractions()方法進行執行。

runAfterInteractions任務也可以接收一個普通的回調函數或者一個帶有gen方法並且返回一個Promise的PromiseTask對象。如果參數是PromiseTask對象,那么任務是異步執行的,也會阻塞。該會等着當前任務執行完畢以后才能執行下一個任務。

默認情況下,隊列任務會一次性在setImmediate方法中批量執行。如果你通過setDeadline方法設置一個時間值,那么任務會在延遲該設定值時間進行執行。這時候會調用setTimeout方法進行掛起任務並且阻塞其他任務的執行。這樣可以給觸摸交互等操作留出時間更好的相應用戶操作。

3、方法與屬性

  1. runAfterInteractions(task) 靜態方法,在用戶交互和動畫結束以后執行任務
  2. createInteractionHandle() 靜態方法,創建一個句柄(處理器),通知管理器,某個動畫或者交互開始了
  3. clearInteractionHandle(handler:Handle) 靜態方法,進行清除句柄,通知管理器,某個動畫或者交互結束了。
  4. setDeadline(deadline:number) 靜態方法, 設置延遲時間,該會調用setTimeout方法掛起並且阻塞所有沒有完成的任務,然后在eventLoopRunningTime到設定的延遲時間后,然后執行setImmediate方法進行批量執行任務
  5. Events:CallExpression
  6. addListener:CallExpression

4、具體使用

開發中關於卡頓的解決:
先在render中渲染一個空視圖,等轉場動畫完成以后,再去渲染世紀的視圖

代碼如下:

//頁面引入InteractionManager import { AppRegistry, StyleSheet, Text, ScrollView, Image, Alert, TouchableOpacity, InteractionManager, View, ImageBackground, DeviceEventEmitte } from "react-native"; class Redeem extends Component { constructor(props) { super(props); this.state = { renderPlaceholderOnly: true//交互管理器延時控制標識 }; } /** * 頁面初始化先渲染空視圖減少頁面轉場時間 */ _renderPlaceholderView = () => { return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> <Image style={{ width: 150, height: 150, backgroundColor: "#f5f5f5" }} source={require("./../../img/task/loading.gif")} /> </View> ); }; render() { //首先渲染空視圖 if (this.state.renderPlaceholderOnly) { return this._renderPlaceholderView(); } return( //頁面dom ... ) } componentDidMount() { //轉場動畫完成之后改變標記值,重新渲染dom InteractionManager.runAfterInteractions(() => { this.setState({ renderPlaceholderOnly: false }); }); } } 

二、事件監聽

場景:在頁面跳轉是如從A頁面路由跳轉到B頁面,需要在B頁面更新了數據,返回A頁面是需要更新A頁面數據

問題:
由於react路由是基於hash來實現的,從A頁面路由跳轉到B頁面,會在URL上拼接頁面對應的hash值,從B頁面在返回A頁面時,去除URL上的hash值,相當於改變了URL,此時A頁面會重新加載,從而達到刷新頁面的效果;

但是react-navigation的路由跳轉是基於棧來實現的,A路由跳轉B,就是一個壓棧的過程,就是把頁面B push到棧頂,從B返回到A是就是pop彈棧,此時對於頁面A來說是無感知的,就是把B頁面從棧中pop出去,此時對於開發者來說就無法感知B頁面的返回操作了;

解決方案:

使用RN神器發送和接收事件DeviceEventEmitter

具體使用如下:

頁面A//先導入DeviceEventEmitter組件 import { StyleSheet, Text, ScrollView, Image, Alert, TouchableOpacity, TouchableWithoutFeedback, TouchableHighlight, InteractionManager, View, FlatList, SectionList, DeviceEventEmitter } from "react-native"; //要在A頁面寫一個接收消息的方法 //在didmount方法中寫好監聽/接收方法,當有消息發送時,這就會接收到,並執行相應方法 componentDidMount() { //收到監聽 事件監聽 //從新手任務頁面或者積分兌換頁面返回需要監聽 然后更新任務首頁總積分 //原因:新手任務頁面和積分兌換頁面都會設計積分的變更,但是返回任務首頁時不會刷新頁面, //所以需要監聽返回狀態,在主動調用接口 this.listener = DeviceEventEmitter.addListener("left", (e) => { //e就是從頁面B發送過來的數據 if (e) { this.getAllTntegral(); } Toast.info(e); }); } } //當然,別忘了要卸載 componentWillUnmount() { // 移除監聽 this.listener.remove(); } 
頁面B //先導入DeviceEventEmitter組件 import { StyleSheet, Text, ScrollView, Image, Alert, TouchableOpacity, View, ImageBackground, InteractionManager, FlatList, DeviceEventEmitter } from "react-native"; //可根據自己的業務場景來實現消息發送的時機,本人用法是在B頁面返回時,給A頁面發消息通知 class NewTask extends Component { static navigationOptions = ({ navigation }) => ({ headerTitle: `${navigation.state.params.title}`, headerLeft: ( <TouchableOpacity style={styles.action} onPress={() => { //給監聽事件賦值 DeviceEventEmitter.emit("left", "back"); navigation.goBack(); }}> <View style={styles.headerLeft} /> </TouchableOpacity> ) // headerBackTitle: `${navigation.state.params.title}` }); }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM