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

React Native:useEffect监听NativeEventEmitter时useState状态无法更新

React Native 原生事件监听状态切换失效问题

我在React Native中实现了按钮点击调用原生模块函数,原生模块通过RCTDeviceEventEmitter发射textColor事件,JS端用NativeEventEmitter捕获事件,每次捕获后切换ncolor这个useState变量(在false/true间切换)来控制Text组件样式。

现有代码

JS端监听代码

const [ncolor, setnColor] = useState(false);
useEffect(() => {
  const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample);
  this.eventListener = eventEmitter.addListener('textColor', event => {
    console.log('event', ncolor, event); // "someValue"
    setnColor(!ncolor);
  });
  return () => {
    this.eventListener.remove(); //Removes the listener
  };
}, []);

原生模块触发函数(Java)

@ReactMethod
public void bangNotification(){
    Log.d("bang", "Bannnngg");
    notificationClick();
}

原生事件发射函数(Java)

public void notificationClick(){
    Log.d("bang", "Notification clicked");
    WritableMap map = Arguments.createMap();
    map.putString("val", "red");
    try{
        reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("textColor", map);
    } catch (Exception e){
        Log.d("ReactNative", "Caught Exceptioin:" + e.getMessage());
    }
}

遇到的问题

  1. 目前切换行为仅在初始化后生效一次,无法反复切换状态值,原因是什么?
  2. 为何必须在useEffect的依赖数组中指定ncolor?即使添加了依赖,监听器能正常触发日志,但状态依旧无法更新,这又是为什么?
useEffect(() => {
  const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample);
  this.eventListener = eventEmitter.addListener('textColor', event => {
    console.log('event', ncolor, event);
    setnColor(!ncolor);
  });
  return () => {
    this.eventListener.remove();
  };
}, [ncolor]); // 添加了ncolor依赖

问题原因及解决方案

1. 初始代码仅生效一次的原因

这是闭包陷阱导致的:useEffect的依赖数组是空的,只会在组件初始化时执行一次。此时事件监听器里的ncolor捕获的是初始化时的状态值(false),后续无论状态怎么更新,监听器里的ncolor永远是初始的false,所以每次调用setnColor(!ncolor)都会把状态设为true,之后再触发事件时,还是基于false取反,状态不会再变化。

2. 添加ncolor依赖后仍失效的原因

当把ncolor加入依赖数组后,每次ncolor变化时,useEffect会重新执行:移除旧的监听器,创建新的监听器。但问题在于,函数组件里没有实例,this指向全局对象,你用this.eventListener存储监听器实例的操作无效,旧监听器并没有被真正移除,导致多个监听器同时存在。

当事件触发时,多个监听器会依次执行:比如第一次状态是false,新监听器触发后把状态设为true;此时useEffect重新执行创建新监听器(捕获的ncolor是true),但旧监听器还在,下次事件触发时,旧监听器把true变回false,新监听器又把false变回true,最终状态看起来没变化,日志里会看到ncolor交替打印,但实际状态被来回修改抵消了。

正确的解决方案

  • 不要用this存储监听器,改用函数组件的useRef来保存监听器实例,确保每次移除的是正确的监听器。
  • 切换状态时,使用函数式更新(setnColor(prev => !prev)),这样不需要依赖ncolor,也能获取到最新的状态值,避免闭包问题。

修改后的JS代码:

const [ncolor, setnColor] = useState(false);
const eventListenerRef = useRef(null); // 用useRef存储监听器

useEffect(() => {
  const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample);
  eventListenerRef.current = eventEmitter.addListener('textColor', event => {
    console.log('event', ncolor, event);
    // 使用函数式更新获取最新状态
    setnColor(prevColor => !prevColor);
  });

  return () => {
    // 移除当前的监听器
    if (eventListenerRef.current) {
      eventListenerRef.current.remove();
      eventListenerRef.current = null;
    }
  };
}, []); // 依赖数组为空,只初始化一次

补充说明

  • 函数式更新setnColor(prev => !prev)的作用是:React会把当前最新的状态值作为参数传给回调函数,这样不管监听器什么时候触发,都能拿到最新的状态,不会受闭包影响。
  • useRef存储的变量不会随组件重新渲染而变化,能始终保存最新的监听器实例,确保清理函数能正确移除监听器,避免内存泄漏和多监听器冲突。

内容的提问来源于stack exchange,提问作者Saheel Sapovadia

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 05:55:19