Expo应用未挂载组件报错崩溃、Firebase监听器取消后仍生效问题求助
问题根源
- 启动屏(SplashLoadingScreen)的Firebase auth状态监听器没有添加useEffect清理逻辑,组件卸载后监听器仍在运行,引发内存泄漏和跨页面触发
- 启动屏中存储取消订阅函数的变量为普通var类型,存在闭包陷阱,部分场景下回调中无法正确拿到取消订阅函数
- 注册页(SignUpScreen)登录成功后重复创建auth状态监听器,且该监听器同样没有清理逻辑,同时完全没有必要新增监听器,直接处理登录接口返回结果即可
- 注册页的通知权限申请函数直接在组件顶层执行,每次重渲染都会重复调用,容易触发已卸载组件的状态更新
修复方案
1. 修复启动屏的监听器逻辑
首先用useRef存储取消订阅函数避免闭包问题,同时在useEffect的清理回调中执行取消订阅,保证组件卸载时一定会销毁监听器:
// 启动屏组件内新增ref存储取消订阅函数 const unsubscribeRef = useRef(null); // 替换原跳转逻辑useEffect useEffect(() => { if (!isReady) return; try { unsubscribeRef.current = firebase.auth().onAuthStateChanged((user) => { if (user) { firebase.firestore().collection("User Details").doc(user.uid).get().then((snap) => { try { // 执行跳转前先销毁监听器 if (unsubscribeRef.current) { unsubscribeRef.current(); unsubscribeRef.current = null; } if(snap.exists){ navigation.reset({ index : 0, routes : [{ name : 'Tab'}], }); }else { navigation.reset({ index : 0, routes : [{ name : 'User'}], }); } } catch(e) {console.log(e)} }); return; } // 无用户场景也要先销毁监听器再跳转 if (unsubscribeRef.current) { unsubscribeRef.current(); unsubscribeRef.current = null; } navigation.reset({ index : 0, routes : [{ name : 'SignUp'}], }); }); } catch(e) {console.log(e)} // 重点:组件卸载时强制清理监听器 return () => { if (unsubscribeRef.current) { unsubscribeRef.current(); unsubscribeRef.current = null; } }; }, [isReady, navigation]);
2. 修复注册页的重复监听问题
删除冗余的confirmation函数,直接在ConfirmCode中处理登录结果,不需要新增auth状态监听器,同时把通知权限申请函数移入useEffect避免重复执行:
// 注册页组件内新增挂载标记位,避免已卸载组件更新状态 const isMountedRef = useRef(true); useEffect(() => { registerForPushNotificationsAsync(); // 组件卸载时更新标记位 return () => { isMountedRef.current = false; }; }, []); // 替换原ConfirmCode逻辑 const ConfirmCode = async () => { if (!isMountedRef.current) return; setIsLoading(true); setConfirmError(undefined); setConfirmInProgress(true); const credential = firebase.auth.PhoneAuthProvider.credential( verificationId, verificationCode ); try { const userCred = await firebase.auth().signInWithCredential(credential); const snap = await firebase.firestore().collection("User Details").doc(userCred.user.uid).get(); if(snap.exists){ navigation.reset({ index : 0, routes : [{ name : 'Tab'}], }); }else { navigation.reset({ index : 0, routes : [{ name : 'User'}], }); } } catch (err) { if (isMountedRef.current) { setConfirmError(err); } console.log(err); } finally { if (isMountedRef.current) { setConfirmInProgress(false); setVerificationId(''); setVerificationCode(''); verificationCodeTextInput.current?.clear(); setIsLoading(false); } } }
内容的提问来源于stack exchange,提问作者Deep Togani
相关产品推荐
相关产品推荐

