Firebase的onAuthStateChanged监听器移除后仍触发该如何正确卸载?
Firebase Auth状态监听器重复触发/无法移除解决方案
核心问题原因
当前代码的问题在于:
- 你声明的
unsubscribe是函数组件内的局部变量,每次组件渲染都会重置,无法持久化保存监听器的卸载方法 - 仅在无关的useEffect清理函数中将其赋值为
undefined,从来没有真正调用firebase.auth().onAuthStateChanged返回的卸载函数,导致监听器在启动页组件卸载后依然存活,登录页触发登录状态变更时,两个页面的监听器同时响应,出现重复导航、操作已卸载组件等崩溃问题 - 没有清理延时器,延时结束后如果启动页已经卸载,调用
setIsReady会触发React状态更新警告
修改步骤
- 用
useRef存储监听器卸载函数,保证跨渲染周期可以正常访问 - 给注册监听器的useEffect添加清理函数,组件卸载/依赖变更时自动执行卸载逻辑
- 补充延时器的清理逻辑,避免内存泄漏
- 补充Firestore查询异常捕获,避免查询失败导致崩溃
修复后代码
import { useState, useEffect, useRef } from 'react'; // 确保导入useRef try{ if(!firebase.apps.length){ firebase.initializeApp(firebaseConfig); } }catch(err){ console.log(err); } const cacheResourcesAsync = async () => { // 此处保留你原有加载图片的逻辑 const images = []; const cacheImages = images.map(image => { return Asset.fromModule(image).downloadAsync(); }); return Promise.all(cacheImages); } export const SplashLoadingScreen = () => { const [isReady, setIsReady] = useState(false); const [isFire, setIsFire] = useState(false); const [isFont, setIsFont] = useState(false); const [isImages, setIsImages] = useState(false); const navigation = useNavigation(); // 用useRef存储卸载函数 const unsubscribeAuth = useRef(null); // 存储延时器id用于清理 const splashTimerRef = useRef(null); //Loading Fonts const [michromaLoaded] = useMichroma({ Michroma_400Regular }); const [latoLoaded] = useLato({ Lato_700Bold }); //Checking if the fonts are ready useEffect(() => { if(michromaLoaded && latoLoaded){ setIsFont(true); } }, [michromaLoaded, latoLoaded]); //Checking if the Images are loaded or not useEffect(() => { cacheResourcesAsync(); setIsImages(true); }, []); //Checking if Firebase is fired up useEffect(() => { if(firebase.apps.length !== 0){ setIsFire(true); } }, [firebase.apps.length]); //Last Check before moving to other screen useEffect(() => { if(isFont && isImages && isFire){ splashTimerRef.current = setTimeout(() => { setIsReady(true); // Animation Purpose }, 5000); } // 清理延时器 return () => { if(splashTimerRef.current) { clearTimeout(splashTimerRef.current); } } }, [isFont, isImages, isFire]); //Moving to other screens useEffect(() => { if(isReady){ unsubscribeAuth.current = firebase.auth().onAuthStateChanged((user) => { if (user) { firebase.firestore().collection("User Details").doc(user.uid).get().then((snap) => { if(snap.exists){ navigation.navigate('Tab'); }else { navigation.navigate('User'); } }).catch(err => console.log('获取用户信息失败', err)); return; } navigation.navigate("SignUp"); }); } // 核心:组件卸载/依赖变更时卸载监听器 return () => { if(unsubscribeAuth.current) { unsubscribeAuth.current(); unsubscribeAuth.current = null; } } }, [isReady, navigation]); SplashScreen.hideAsync(); // 此处保留你原有渲染逻辑 }
额外注意
- 登录页也需要按照相同的逻辑,给
onAuthStateChanged监听器添加清理逻辑,避免同样的内存泄漏问题 - 后续如果有多个页面都需要监听登录状态,建议将监听器抽离到全局状态管理(比如Context、Redux)中,统一管理监听逻辑,避免多页面重复注册监听器
内容的提问来源于stack exchange,提问作者Deep Togani
相关产品推荐
相关产品推荐

