Expo React Native使用Firebase获取认证用户及资料偶发null错误求解
问题根因分析
- 你对
setUser、setProfile加了await,但React的状态更新函数本身不返回Promise,await不会等待状态更新完成就会执行后续的setIsLoading(false),导致偶尔出现导航已经跳转到HomeStack,但profile状态还没更新完成的情况,这就是偶发null的核心原因。 - 目前仅在Auth状态变化时单次拉取Firestore数据,后续如果用户资料在其他端/场景更新,前端无法同步,需要手动刷新才能拿到新数据。
- 边界场景未处理:如果是刚注册的新用户,
members集合下还没有对应用户uid的文档,profile会一直为null,没有兜底逻辑。
修复方案
将身份认证、资料监听的逻辑全部收敛到AuthenticatedUserProvider中,职责划分更清晰,同时给Firestore添加实时快照监听,资料更新后全局自动同步:
1. 修改 AuthenticatedUserProvider.js
import React, { useState, createContext, useEffect } from 'react'; import { auth, fs } from '../config/firebase'; export const AuthenticatedUserContext = createContext({}); export const AuthenticatedUserProvider = ({ children }) => { const [user, setUser] = useState(null); const [profile, setProfile] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { let unsubscribeProfile = null; // 监听Auth状态变化 const unsubscribeAuth = auth.onAuthStateChanged(async (authenticatedUser) => { try { // 先清除之前的profile监听,避免内存泄漏 if (unsubscribeProfile) { unsubscribeProfile(); unsubscribeProfile = null; } if (authenticatedUser) { setUser(authenticatedUser); // 监听对应用户的profile文档变化,实时更新 unsubscribeProfile = fs.collection("members") .doc(authenticatedUser.uid) .onSnapshot((snapshot) => { if (snapshot.exists) { setProfile(snapshot.data()); } else { setProfile(null); // 可在这里添加新用户无profile的兜底逻辑,比如跳转补全资料页 } // 所有数据加载完成后再关闭loading setIsLoading(false); }, (error) => { console.log("拉取用户资料失败:", error); setProfile(null); setIsLoading(false); }); } else { setUser(null); setProfile(null); setIsLoading(false); } } catch (error) { console.log("身份状态处理失败:", error); setUser(null); setProfile(null); setIsLoading(false); } }); // 组件卸载时清除所有监听 return () => { unsubscribeAuth(); if (unsubscribeProfile) { unsubscribeProfile(); } }; }, []); return ( <AuthenticatedUserContext.Provider value={{ user, profile, isLoading }}> {children} </AuthenticatedUserContext.Provider> ); };
2. 简化 RootNavigator.js
import React, { useContext } from 'react'; import { View, ActivityIndicator } from 'react-native'; import { NavigationContainer } from '@react-navigation/native'; import * as eva from '@eva-design/eva'; import { ApplicationProvider, IconRegistry } from '@ui-kitten/components'; import { EvaIconsPack } from '@ui-kitten/eva-icons'; import { AuthenticatedUserContext } from './AuthenticatedUserProvider'; import AuthStack from './AuthStack'; import HomeStack from './HomeStack'; import { default as theme } from '../orange-theme.json'; export default function RootNavigator() { const { user, isLoading } = useContext(AuthenticatedUserContext); if (isLoading) { return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <ActivityIndicator size='large' /> </View> ); } return ( <> <IconRegistry icons={EvaIconsPack} /> <ApplicationProvider {...eva} theme={{ ...eva.light, ...theme }}> <NavigationContainer> {user ? <HomeStack /> : <AuthStack />} </NavigationContainer> </ApplicationProvider > </> ); }
额外注意事项
- 新用户注册完成后,要立即在
members集合下创建对应用户uid的文档,避免出现profile为空的情况 - 可根据业务需求,在HomeStack的页面中加
profile为空的兜底占位UI,进一步避免报错 - 如需要支持离线使用,可开启Firestore的离线持久化功能,无网络时也能读取到缓存的profile数据
内容的提问来源于stack exchange,提问作者Daniel Adinugroho
相关产品推荐
相关产品推荐

