React Native加载时重复订阅WebSocket 需实现手动单订阅
React Native 连接 Rails ActionCable 重复订阅问题修复
问题根源
你的代码存在几个核心问题导致重复订阅:
- 组件每次重新渲染时,
identifier都会生成新UUID,进而创建大量不同名称的频道 - 全局创建的
actionCable和cable实例可能在组件多次挂载时重复初始化 - 回调函数依赖数组不完整,导致重复触发订阅逻辑
- 断开连接时的清理逻辑不彻底,残留旧订阅实例
修改后的完整代码
import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { View, Text } from 'react-native'; import { Button } from 'react-native-paper'; import { ActionCable, Cable } from '@kesha-antonov/react-native-action-cable'; import UUID from 'react-native-uuid'; const App: React.FC = () => { const [isWebsocketConnected, setIsWebsocketConnected] = useState(false); const [dchannel, setDchannel] = useState<any>(null); // 组件生命周期内仅生成一次唯一标识 const identifier = useMemo(() => UUID.v4(), []); // 确保ActionCable消费者和Cable实例只初始化一次 const actionCable = useMemo(() => { return ActionCable.createConsumer('wss://blabla.ngrok-free.app/cable'); }, []); const cable = useMemo(() => new Cable({}), []); const handleReceived = useCallback(({ type, message }) => { console.log(type); console.log(message); console.log("triggered"); }, []); const handleConnected = useCallback(() => { // 防止重复创建订阅 if (isWebsocketConnected || dchannel) return; const channel = cable.setChannel( `wire_channel_${identifier}`, actionCable.subscriptions.create({ channel: 'WireChannel', }) ); // 绑定消息接收监听 channel.on('received', handleReceived); setDchannel(channel); setIsWebsocketConnected(true); }, [isWebsocketConnected, dchannel, identifier, cable, actionCable, handleReceived]); const removeChannel = useCallback(() => { if (!dchannel) return; const channelName = `wire_channel_${identifier}`; const channel = cable.channel(channelName); if (channel) { channel.removeListener('received', handleReceived); channel.unsubscribe(); delete cable.channels[channelName]; } setDchannel(null); }, [dchannel, identifier, cable, handleReceived]); const handleDisconnected = useCallback(() => { removeChannel(); setIsWebsocketConnected(false); }, [removeChannel]); const handleSendMessage = useCallback(() => { if (!dchannel) return; dchannel.perform('speak', { message: 'Hey', identifier: identifier }); }, [dchannel, identifier]); // 组件卸载时自动清理订阅 useEffect(() => { return () => { removeChannel(); }; }, [removeChannel]); return ( <View style={{ flex: 1 }}> <View style={{ padding: 20, backgroundColor: '#f0f0f0', alignItems: 'center' }}> <Text style={{ fontSize: 24 }}>Your Logo Here</Text> </View> <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', gap: 16 }}> <Button mode="contained" onPress={handleConnected} color="#00C853" disabled={isWebsocketConnected} > Connect WebSocket </Button> <Button mode="contained" onPress={handleDisconnected} color="#FF1744" disabled={!isWebsocketConnected} > Disconnect WebSocket </Button> <Button mode="contained" onPress={handleSendMessage} disabled={!isWebsocketConnected} > Send Message </Button> <Text> WebSocket Status: {isWebsocketConnected ? 'Connected' : 'Disconnected'} </Text> </View> </View> ); }; export default App;
关键修改说明
- 持久化唯一标识:用
useMemo生成identifier,确保组件生命周期内只生成一次,避免创建大量无关频道 - 实例单例化:用
useMemo包裹actionCable和cable创建逻辑,保证只初始化一次连接实例 - 连接逻辑防护:在
handleConnected中增加状态判断,防止重复触发订阅 - 完整依赖数组:给所有
useCallback补充完整依赖,避免因依赖缺失导致的逻辑重复执行 - 彻底清理逻辑:在
removeChannel中移除监听、取消订阅并删除频道实例,组件卸载时自动执行清理 - 发送逻辑优化:直接使用已保存的
dchannel实例,避免重复查找频道,同时增加空值判断防止报错
内容的提问来源于stack exchange,提问作者Leon
相关产品推荐
相关产品推荐

