React Native Firebase中useEffect如何等待async函数执行后导航
问题原因
你当前的代码里getUser内部已经用await等待getDoc返回结果后才执行导航,不存在“没等异步执行完就跳转”的问题,感知到的数秒延迟来自三个设计缺陷:
- 没有做校验阶段的加载拦截:页面初始化时直接渲染了手机号登录表单,从auth状态校验、到Firestore查询用户记录的这几秒网络耗时里,用户能完整看到登录页,等查询完成再跳转就会有明显的迟滞感
- 存在异步竞态隐患:
onAuthStateChanged的回调默认是同步执行的,你直接在里面调用async声明的getUser但没有显式await,会导致Promise悬空,加上React18严格模式下useEffect会双执行,容易出现重复触发导航、状态更新不同步的问题 - 你打印的
Before getUser/After getUser日志会在getUser执行完成之前就输出,这是JS事件循环机制下异步函数的正常表现,不代表导航逻辑没等查询结果
修复方案
1. 新增加载状态拦截渲染
加独立的加载状态标记,所有校验流程没走完之前,只渲染全局加载提示,不渲染登录表单,从视觉上消除延迟感。
2. 给auth回调加async/await,加防重复执行标记
把onAuthStateChanged的回调改成async函数,直接await getUser执行,同时用ref做防重标记,避免组件卸载后或者重复触发回调的时候执行无效导航、更新已卸载组件的状态。
3. 补全异常捕获
给Firestore查询加try/catch逻辑,请求失败的时候给用户明确提示,不要卡白屏。
核心修改代码
首先在组件顶部补状态和防重标记:
// 新增全局加载状态 const [isLoading, setIsLoading] = useState(true); // 新增防重复导航标记,解决竞态问题 const isNavigated = useRef(false);
然后替换原有的useEffect逻辑:
useEffect(() => { // 进入页面先初始化加载状态 setIsLoading(true); isNavigated.current = false; const getUser = async (phoneNumber) => { const docRef = doc(db, "users", phoneNumber); const docSnap = await getDoc(docRef); // 已经完成跳转就直接返回,不重复执行逻辑 if (isNavigated.current) return; if (docSnap.exists()) { isNavigated.current = true; // 用replace代替navigate,避免用户按返回键回到校验页 navigation.replace("MainHome"); } else { isNavigated.current = true; setPhone(phoneNumber); navigation.replace("SignupLanguage"); } }; // 将auth状态回调改为async函数,显式等待异步查询完成 const unsubscribe = onAuthStateChanged(auth, async (user) => { try { if (!user) { // 未登录状态直接关闭加载态,展示登录表单 setIsLoading(false); return; } // 已登录状态等待用户信息查询完成再走后续逻辑 await getUser(user.phoneNumber); } catch (err) { console.log("身份校验失败", err); setIsLoading(false); showMessage({ text: `身份校验失败: ${err.message}`, color: "red" }); } }); return () => { unsubscribe(); // 组件卸载时标记已完成跳转,避免内存泄漏 isNavigated.current = true; }; }, [isSigned]);
最后在组件return的最开头加加载拦截:
// 校验过程中只渲染加载提示 if (isLoading) { return ( <View style={[styles.containerGeneral, {justifyContent: 'center', alignItems: 'center'}]}> <Text>身份校验中...</Text> </View> ) } // 校验完成再渲染原有的登录表单内容 return ( <View style={styles.containerGeneral}> {/* 原有登录页渲染代码保持不变 */} </View> )
额外优化点
- 导航统一用
navigation.replace代替navigation.navigate,跳转后路由栈不会保留当前登录校验页,用户按返回键不会出现异常回退 - 如果Firestore查询平均耗时过长,可以给查询加3-5秒的超时逻辑,超时后直接提示用户检查网络,不要一直卡在加载态
- 长期来看可以把auth状态校验、用户信息查询的逻辑抽到全局路由守卫中,不用每个登录相关页面单独写校验逻辑,减少重复代码
内容的提问来源于stack exchange,提问作者Mikhail L
相关产品推荐
相关产品推荐

