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/大对象缓存。
更多推荐

所有评论(0)