React对接Firebase获取用户详情渲染时页面空白报错
本质就是异步数据的初始空值没做兜底:
你写的GetUserDetails Hook初始state是null,不管是Firebase的auth状态监听,还是后续Firestore的用户文档查询,都是异步执行的——组件第一次挂载渲染的时候,这些异步逻辑还没跑完,Hook返回的userDetails就是初始值null,这时候你直接写userDetails.Address,相当于读取null上的Address属性,直接抛JS类型错误,整个React组件树崩溃,就是你看到的白屏。
你之前测的“先删掉详情渲染代码,等页面加载完再加回去就正常”,是因为这时候异步请求早就跑完了,userDetails已经从null变成了存着用户信息的实际对象,再访问属性当然不会报错。但只要路由跳转重新进入页面,组件会重新走挂载流程,又会先拿到null的初始值渲染,所以每次进页面都必现白屏。
另外你现在写的三个Hook还有额外的问题:每个Hook都单独注册了一次auth状态监听,还重复请求了多次users集合的文档,既浪费性能,又没有写监听取消逻辑,组件卸载的时候会造成内存泄漏。
分两步改,先解决眼前的白屏问题,再优化逻辑解决潜在隐患。
1. 先给渲染逻辑加空值兜底,立刻解决白屏
在数据没加载完成的时候,不要直接访问可能为null的对象属性,用可选链操作符加兜底内容就行:
<tr> <td> {userDetails?.Address ?? '加载中'}<br /> </td> <td> {userDetails ? `${userDetails.FirstName} ${userDetails.LastName}` : '加载中'}<br /> {userDetails?.Email ?? ''} </td> </tr>
改完之后哪怕第一次渲染userDetails是null,也不会抛错,等异步数据回来之后页面会自动更新成正确内容。
2. 合并冗余Hook,补上状态管理和清理逻辑
你现在拆的GetCurrentUser、GetUserID、GetUserDetails三个Hook逻辑高度重合,完全没必要分开写,合并成一个统一的用户Hook就行,顺便加上加载状态、错误处理,还有组件卸载时的监听取消逻辑,避免内存泄漏:
function useUserInfo() { const [userInfo, setUserInfo] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { // 注册auth监听,拿到取消订阅的方法 const unSubAuth = auth.onAuthStateChanged(async (authUser) => { try { setLoading(true); if (!authUser) { setUserInfo(null); return; } // 查用户自定义信息 const userDocSnap = await db.collection("users").doc(authUser.uid).get(); // 合并auth自带字段和Firestore存的自定义字段,一次返回所有需要的内容 setUserInfo({ uid: authUser.uid, email: authUser.email, ...userDocSnap.data() }); } catch (err) { setError(err); } finally { setLoading(false); } }); // 组件卸载时取消订阅,防止内存泄漏 return () => unSubAuth(); }, []); return { userInfo, loading, error }; }
3. 组件里配合加载状态使用,体验更好
在组件里调用的时候,先判断加载状态,等数据准备好再渲染内容,就不需要到处写空值判断了:
const { userInfo, loading, error } = useUserInfo(); // 加载中展示占位 if (loading) return <tr><td colSpan={2}>加载用户信息中...</td></tr>; // 报错展示提示 if (error) return <tr><td colSpan={2}>用户信息加载失败,请刷新重试</td></tr>; // 未登录可以在这里加跳转登录页的逻辑 if (!userInfo) return <tr><td colSpan={2}>请先登录</td></tr>; // 走到这里userInfo一定是有值的,直接访问属性不会有任何问题 return ( <tr> <td> {userInfo.Address}<br /> </td> <td> {userInfo.FirstName} {userInfo.LastName}<br /> {userInfo.Email} </td> </tr> );
内容的提问来源于stack exchange,提问作者Omar Assouma

