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/大对象缓存。

相关推荐
Alice-YUE8 分钟前
前端 × AI:从 Cursor 到 Transformer,一份完整认知路径
前端·人工智能·transformer·ai编程·前端开发·cursor
Code_Solitude30 分钟前
C语言:关于二维数组的作业总结
java·c语言·前端
专业程序开发源1 小时前
django新闻推荐系统70655-计算机课程设计、毕业设计
java·javascript·spring boot·后端·python·django·课程设计
acd120091 小时前
前端随笔:数据明明变了,Vue 页面就是不更新
前端·javascript·vue.js
传人2 小时前
悬停旋转放大和位移效果如何写
前端·javascript·css
羲云3 小时前
给 Vue 页面加个 Markdown 编辑器:ME.js 的接入、图片粘贴与音视频
javascript·vue.js
zhangzeyuaaa3 小时前
Ruby 方法参数完全指南:默认值、可变参数与关键字参数
前端·python·ruby
粥里有勺糖3 小时前
视野修炼-技术周刊第135期 | 鼠标跟随宠物
前端·github·aigc
粥里有勺糖3 小时前
拿 Codex 协助清理磁盘,Nice!
前端·后端·github
Highcharts.js3 小时前
可视化商用图表库对比,如何选择?
前端·javascript·学习·信息可视化·highcharts·前端可视化