react navite 内存泄漏:定时器、监听事件、订阅销毁

React Native 里"内存泄漏"很大一部分其实来自 副作用没有在组件卸载/依赖变化时正确清理 。重点检查这三类:定时器、事件监听、订阅

1. 定时器:setInterval / setTimeout

❌ 容易泄漏:

复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log('执行中');
  }, 1000);
}, []);

组件卸载后,setInterval 还会继续执行。

✅ 正确:

复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log('执行中');
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, []);

setTimeout 同理:

复制代码
useEffect(() => {
  const timer = setTimeout(() => {
    console.log('执行一次');
  }, 3000);

  return () => {
    clearTimeout(timer);
  };
}, []);

2. 事件监听:addEventListener

React Native 常见的:

  • AppState

  • Keyboard

  • Dimensions

  • BackHandler

  • NetInfo

  • DeviceEventEmitter

  • Navigation events

例如:

❌:

复制代码
useEffect(() => {
  AppState.addEventListener('change', handleAppStateChange);
}, []);

这会导致监听器一直存在。

✅:

复制代码
useEffect(() => {
  const subscription = AppState.addEventListener(
    'change',
    handleAppStateChange
  );

  return () => {
    subscription.remove();
  };
}, []);

Keyboard

复制代码
useEffect(() => {
  const subscription = Keyboard.addListener('keyboardDidShow', () => {
    console.log('键盘打开');
  });

  return () => {
    subscription.remove();
  };
}, []);

BackHandler

复制代码
useEffect(() => {
  const subscription = BackHandler.addEventListener(
    'hardwareBackPress',
    handleBackPress
  );

  return () => {
    subscription.remove();
  };
}, []);

3. 订阅:Observable / WebSocket / EventEmitter

例如 RxJS:

❌:

复制代码
useEffect(() => {
  const subscription = observable.subscribe(data => {
    setData(data);
  });
}, []);

✅:

复制代码
useEffect(() => {
  const subscription = observable.subscribe(data => {
    setData(data);
  });

  return () => {
    subscription.unsubscribe();
  };
}, []);

WebSocket:

复制代码
useEffect(() => {
  const ws = new WebSocket('wss://example.com');

  ws.onmessage = event => {
    console.log(event.data);
  };

  return () => {
    ws.close();
  };
}, []);

4. 一个很容易忽略的问题:闭包

例如:

复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log(user);
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, []);

虽然 timer 清理了,但它可能持有第一次 render 的 user

如果 user 变化后你需要使用最新值:

复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log(user);
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, [user]);

不过这样会导致 user 每次变化都重新创建定时器。

如果定时器应该只创建一次,可以使用 useRef

复制代码
const userRef = useRef(user);

useEffect(() => {
  userRef.current = user;
}, [user]);

useEffect(() => {
  const timer = setInterval(() => {
    console.log(userRef.current);
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, []);

5. 网络请求也是一个重点

比如:

复制代码
useEffect(() => {
  fetch('/api/user')
    .then(res => res.json())
    .then(data => {
      setUser(data);
    });
}, []);

如果页面已经卸载,请求可能仍然在进行。

可以使用 AbortController

复制代码
useEffect(() => {
  const controller = new AbortController();

  fetch('/api/user', {
    signal: controller.signal,
  })
    .then(res => res.json())
    .then(data => {
      setUser(data);
    })
    .catch(error => {
      if (error.name !== 'AbortError') {
        console.error(error);
      }
    });

  return () => {
    controller.abort();
  };
}, []);

6. RN 项目建议统一记住这个原则

看到:

复制代码
useEffect(() => {
  // 创建资源
}, []);

马上问自己:

这个资源什么时候销毁?

对应关系基本是:

创建 清理
setInterval clearInterval
setTimeout clearTimeout
AppState.addEventListener subscription.remove()
Keyboard.addListener subscription.remove()
BackHandler.addEventListener subscription.remove()
RxJS subscribe unsubscribe()
WebSocket close()
fetch AbortController.abort()
EventEmitter on off/removeListener
Navigation listener unsubscribe()

核心模式就是:

复制代码
useEffect(() => {
  // 创建
  const resource = createSomething();

  return () => {
    // 销毁
    resource.destroy();
  };
}, []);

另外,React Native 中还有一个非常常见的误区:"页面看起来离开了" ≠ "组件卸载了" 。尤其是 React Navigation,navigate 到另一个页面后,前一个 Screen 可能仍然 mounted。

所以如果你的问题是 "RN 页面切换几次以后内存越来越高" ,除了 useEffect cleanup,还应该重点检查 React Navigation Screen 是否实际卸载、Focus/Blur 监听、WebSocket、Native Module EventEmitter、FlatList/大对象缓存

相关推荐
benbenAItalk42 分钟前
数字人口播视频的批量生产实践:素材规范、任务编排与质量验收
java·前端·音视频
2601_962885721 小时前
如何用 Python 扫描 A 股跳空缺口并统计缺口回补概率?
java·前端·python
西瓜太郎12342 小时前
Claude Code、Codex CLI、Gemini CLI 能否共用一枚 Key?先看协议选择矩阵
前端·api 网关·claude code·gemini cli·codex cli
多加点辣也没关系2 小时前
JavaScript|第31章:表单与控件
开发语言·javascript·ecmascript
陈随易3 小时前
在Finch用了62亿词元,我认为这是新一代Agent工具之神
前端·人工智能·后端
水域安全老周4 小时前
水趣钓鱼救生衣专利拆解:两级锁紧如何解决落水人衣分离
java·前端·网络
传奇开心果编程4 小时前
【Xilem基础语法学与练】第7课:Xilem与Masonry底层构建技术解析
学习·rust·前端框架
计算机魔术师4 小时前
Anthropic CEO突然喊踩刹车,OpenAI罕见力挺:AI这辆车不能只踩油门了
前端
wing985 小时前
从codex转战workbuddy使用一周的感受
前端·人工智能·后端
Highcharts.js5 小时前
常见报错排雷指南2:导出失败的官方解法
javascript·react.js·ecmascript·highcharts·可视化图表·导出模块失败·导出服务