You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 07:20:00