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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐