React Native中如何处理WebSocket高频数据流以避免界面冻结?
高效处理React Native中WebSocket高频实时数据的方案
核心优化思路
因为数据仅需展示无需持久化,核心是避免全局状态更新引发的全组件重渲染,改用局部状态+组件拆分+非状态变量结合的方式,同时优化WebSocket的连接管理。
具体优化步骤
1. 用useRef替代useState存储实时数据
useRef的更新不会触发组件重渲染,适合存储仅用于展示的高频数据:
// 替换原来的useState const marketDataRef = useRef({});
2. 拆分独立展示组件并使用memo包装
将每个标的的展示拆分为独立组件,用React.memo包裹,只有当props变化时才重渲染:
// 新建单个标的展示组件 import React, { memo } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; const TickerItem = memo(({ label, value }) => ( <View style={{flexDirection:'column'}}> <Text style={{fontSize:25, color:'#4c80cf', fontWeight:'500',textAlign:'center'}}>{label}</Text> <TouchableOpacity> <Text style={{fontSize:30, fontWeight:'800',alignSelf:'center'}}>{value}</Text> </TouchableOpacity> </View> )); export default TickerItem;
3. 优化WebSocket连接与数据更新逻辑
- 将WebSocket实例移到
useEffect内部,避免组件重复创建连接 - 直接更新
useRef的值,并用forceUpdate触发局部组件刷新(仅更新需要变化的部分)
修改后的HomeScreen核心代码:
import { View, Text, SafeAreaView, Dimensions, TouchableOpacity } from 'react-native' import React, { useEffect, useRef, useState } from 'react' import { ScrollView } from 'react-native-gesture-handler' import Strikes from './Strikes'; import TickerItem from './TickerItem'; const width = Dimensions.get('window').width const height = Dimensions.get('window').height export default function HomeScreen() { const marketDataRef = useRef({}); const wsRef = useRef(null); const [, forceUpdate] = useState({}); // 用于局部触发更新 const requestArray = ["nse_cm_nifty_50","nse_cm_nifty_bank","nse_fno_banknifty_05012023_43000_ce","nse_fno_banknifty_05012023_43000_pe","nse_fno_banknifty_05012023_43100_ce","nse_fno_banknifty_05012023_43100_pe","nse_fno_banknifty_05012023_43200_ce","nse_fno_banknifty_05012023_43200_pe","nse_fno_banknifty_05012023_43300_ce","nse_fno_banknifty_05012023_43300_pe","nse_fno_banknifty_05012023_43400_ce","nse_fno_banknifty_05012023_43400_pe","nse_fno_banknifty_05012023_43500_ce","nse_fno_banknifty_05012023_43500_pe"] useEffect(() => { const url = "WebsocketURL"; wsRef.current = new WebSocket(url); const ws = wsRef.current; ws.onopen = () => { const requestString = requestArray.join(','); ws.send(`type=msubscribe&${requestString}&columns=20`); }; ws.onmessage = async (e) => { const res = await new Response(e.data).text(); const responsedata = res.split('\n'); const updatedData = {...marketDataRef.current}; for (let i=0; i<responsedata.length-2; i++) { const tempdata = responsedata[i].split('|'); updatedData[tempdata[0]] = tempdata[1]; } marketDataRef.current = updatedData; forceUpdate({}); // 触发组件更新,仅memo后的子组件会按需重渲染 }; ws.onerror = (e) => { console.log(e.message); }; ws.onclose = (e) => { console.log(e.code, e.reason); }; // 组件卸载时关闭连接 return () => { ws.close(); }; }, []); return ( <SafeAreaView style={{flex:1 ,backgroundColor:'#ebecee',shadowColor:'black',borderBottomStartRadius:10,borderBottomEndRadius:10}}> <View> <View style={{padding:20}}> <Text style={{fontSize:30,fontWeight:'bold'}}>Marketwatch</Text> </View> <View style={{flexDirection:'row',marginTop:5,justifyContent:'space-around'}}> <TickerItem label="NIFTY 50" value={marketDataRef.current.nse_cm_nifty_50} /> <TickerItem label="BANK NIFTY" value={marketDataRef.current.nse_cm_nifty_bank} /> </View> <View style={{width:width-20,borderRadius:5,backgroundColor:'#810a0a',height:60,justifyContent:'space-around',alignItems:'center',alignSelf:'center',marginTop:10,marginBottom:5,flexDirection:'row'}}> <Text style={{color:'white',fontSize:25,fontWeight:'400'}}>Expiry </Text> <Text style={{color:'white',fontSize:25,fontWeight:'400'}}>5-DEC-2023</Text> </View> </View> <ScrollView style={{flex:1 ,backgroundColor:'white',width:width,marginTop:5,height:height}} showsVerticalScrollIndicator={false}> <View style={{flex:1, padding:20,flexDirection:'row',alignItems:'center',justifyContent:'space-between',marginTop:5,marginBottom:0}}> <Text style={{color:'green',fontSize:15,fontWeight:'800'}}>CALLS</Text> <Text style={{color:'black',fontSize:20,fontWeight:'800',textDecorationLine:'underline'}}>STRIKE</Text> <Text style={{color:'red',fontSize:15,fontWeight:'800'}}>PUTS </Text> </View> <View style={{ borderBottomColor: '#ebecee', borderBottomWidth:2,}}/> <Strikes strike={43000} ce={marketDataRef.current.nse_fno_banknifty_05012023_43000_ce} pe={marketDataRef.current.nse_fno_banknifty_05012023_43000_pe}/> <Strikes strike={43100} ce={marketDataRef.current.nse_fno_banknifty_05012023_43100_ce} pe={marketDataRef.current.nse_fno_banknifty_05012023_43100_pe}/> <Strikes strike={43200} ce={marketDataRef.current.nse_fno_banknifty_05012023_43200_ce} pe={marketDataRef.current.nse_fno_banknifty_05012023_43200_pe}/> <Strikes strike={43300} ce={marketDataRef.current.nse_fno_banknifty_05012023_43300_ce} pe={marketDataRef.current.nse_fno_banknifty_05012023_43300_pe}/> <Strikes strike={43400} ce={marketDataRef.current.nse_fno_banknifty_05012023_43400_ce} pe={marketDataRef.current.nse_fno_banknifty_05012023_43400_pe}/> <Strikes strike={43500} ce={marketDataRef.current.nse_fno_banknifty_05012023_43500_ce} pe={marketDataRef.current.nse_fno_banknifty_05012023_43500_pe}/> </ScrollView> </SafeAreaView> ) }
4. 给Strikes组件添加memo包装
确保Strikes组件只有当props变化时才重渲染:
// 修改Strikes组件 import React, { memo } from 'react'; // ... 原有代码 const Strikes = memo(({ strike, ce, pe }) => { // ... 原有渲染逻辑 }); export default Strikes;
额外优化建议
- 数据防抖:如果更新频率过高(比如每秒几十次),可以添加防抖逻辑,控制UI更新频率,避免过度渲染:
import { debounce } from 'lodash'; const updateUI = debounce(() => { forceUpdate({}); }, 50); // 每50ms更新一次UI // 在onmessage中调用updateUI替代直接forceUpdate marketDataRef.current = updatedData; updateUI(); - WebSocket连接复用:如果多个页面需要WebSocket连接,考虑将连接逻辑封装到自定义Hook中,避免重复创建连接。
内容的提问来源于stack exchange,提问作者Badineni Saivardhan
相关产品推荐
相关产品推荐

