React Native页面跳转后事件监听器未注销问题求助
问题根源
你的问题出在事件监听器的注册与注销不匹配:
RegisterScanner里调用了eventEmitter.addListener添加JS层的监听,但UnregisterScanner只调用了原生模块的unregisterScannerStatusListener,没有移除JS侧的监听器,导致OrderDetails组件的监听回调还留在事件队列里。- 另外,原生模块的注册/注销逻辑如果是全局单例模式,多次调用注册可能会导致回调叠加,而注销没有彻底清理。
修复步骤
1. 重构事件监听器工具函数
修改RegisterScanner和UnregisterScanner,让它们能正确管理JS侧的监听器引用:
import {NativeEventEmitter, NativeModules} from 'react-native'; const {BarcodeScannerModule} = NativeModules; const eventEmitter = new NativeEventEmitter(BarcodeScannerModule); // 保存所有注册的监听器引用,用于后续移除 const scannerListeners = new Set(); export const RegisterScanner = handleScannerStatus => { const listener = eventEmitter.addListener('onBarcodeScanned', handleScannerStatus); scannerListeners.add(listener); // 只在没有注册过原生监听时调用(避免重复注册) if (scannerListeners.size === 1) { BarcodeScannerModule.registerScannerStatusListener(); } return listener; }; export const UnregisterScanner = (listener) => { // 移除指定的JS监听器 if (listener) { listener.remove(); scannerListeners.delete(listener); } // 当所有JS监听器都移除后,再注销原生监听 if (scannerListeners.size === 0) { BarcodeScannerModule.unregisterScannerStatusListener(); } }; // 可选:添加全局清理函数,防止内存泄漏 export const UnregisterAllScanners = () => { scannerListeners.forEach(listener => listener.remove()); scannerListeners.clear(); BarcodeScannerModule.unregisterScannerStatusListener(); };
2. 修正OrderDetails组件的监听逻辑
在组件中保存监听器引用,确保组件卸载时能精准移除对应的监听:
const OrderDetails = ({route}) => { const {OrderNumber} = route.params; const [items, setItems] = useState(null); const navigation = useNavigation(); // 保存监听器引用 const scannerListenerRef = useRef(null); useEffect(() => { const fetchItemDetails = async () => { try { const response = await axios.get( `http://192.168.X.XX:5000/orders/${OrderNumber}`, ); setItems(response.data); const handleScannerStatus = data => { onBarcodeScan(data.data, response.data.items); }; // 保存监听器引用 scannerListenerRef.current = RegisterScanner(handleScannerStatus); } catch (error) { console.log('error fetching order'); } }; fetchItemDetails(); return () => { console.log('component unmounted'); // 用保存的引用注销监听 UnregisterScanner(scannerListenerRef.current); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [OrderNumber]); // ... 其余代码不变 };
3. 修正KitDetails组件的监听逻辑
同样保存监听器引用,确保卸载时清理:
const KitDetailsScreen = ({route}) => { const {OrderNumber, kit} = route.params; const scannerListenerRef = useRef(null); useEffect(() => { const handleScannerStatus = data => { console.log('Logging Barcode From KitDetails'); }; // 保存监听器引用 scannerListenerRef.current = RegisterScanner(handleScannerStatus); return () => { console.log('unregister here'); UnregisterScanner(scannerListenerRef.current); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // ... 其余代码不变 };
4. 检查原生模块逻辑(关键)
如果以上修改后问题仍存在,需要确认原生模块的registerScannerStatusListener和unregisterScannerStatusListener逻辑:
- 确保
unregister能彻底移除原生侧的回调,不会残留之前的监听 - 避免全局单例的回调被多次添加,比如每次注册都新增一个回调而不是替换
- 如果原生模块是广播事件模式,需要确保注销后不再发送事件到JS层
额外验证点
- 跳转时观察控制台是否输出
component unmounted,确认OrderDetails的清理函数是否执行 - 可以在
UnregisterScanner中添加日志,检查是否正确移除了监听器
内容的提问来源于stack exchange,提问作者Chrissisbeast
相关产品推荐
相关产品推荐

