React自定义Hook监听Firestore文档时isUserLoading始终为false
问题原因及修复方案
核心问题:onSnapshot的使用方式错误
你在代码中用了await onSnapshot(...),但**onSnapshot并不是异步Promise函数**——它是同步执行的,会立即返回一个取消订阅的函数,而非Promise对象。这就导致代码不会等待快照回调触发,直接跳过进入finally块,立刻把isUserLoading设为false,所以你看到的状态始终是false。
除此之外,代码还有几个潜在问题需要修正:
auth.currentUser可能在组件初始化时为null(Firebase Auth初始化需要时间),直接访问uid会抛出错误- 没有清理
onSnapshot的订阅,组件卸载时会造成内存泄漏 useEffect依赖数组为空,当用户登录状态变化时,不会重新触发订阅
修复后的完整代码
import { doc, onSnapshot } from 'firebase/firestore'; import { useEffect, useState } from 'react'; import { db, auth } from '../../firebase'; function useCurrentUser() { const [user, setUser] = useState({}); const [isUserLoading, setIsUserLoading] = useState(true); // 初始设为true更符合加载逻辑 const [isUserError, setIsUserError] = useState(null); useEffect(() => { let unsub = null; const setupUserListener = () => { setIsUserLoading(true); // 先检查用户是否已登录 const currentUser = auth.currentUser; if (!currentUser) { setIsUserError(new Error('用户未登录')); setIsUserLoading(false); return; } const userRef = doc(db, 'users', currentUser.uid); // onSnapshot的错误需要通过第二个回调参数捕获,而非try/catch unsub = onSnapshot( userRef, (doc) => { setUser(doc.data()); setIsUserLoading(false); // 拿到数据后再结束加载状态 }, (error) => { setIsUserError(error); setIsUserLoading(false); } ); }; setupUserListener(); // 返回清理函数,组件卸载时取消Firestore监听 return () => { if (unsub) unsub(); }; }, [auth.currentUser]); // 依赖用户状态,登录/登出时重新订阅 return { user, isUserLoading, isUserError }; } export default useCurrentUser;
关键修复点说明
- 移除
await,直接使用onSnapshot,并在快照回调和错误回调中分别处理加载状态 - 初始将
isUserLoading设为true,符合组件启动时的加载预期 - 增加
auth.currentUser的空值检查,避免初始化时的报错 - 在
useEffect依赖数组中加入auth.currentUser,确保用户状态变化时重新建立监听 - 返回取消订阅函数,清理Firestore的实时监听,防止内存泄漏
- 将错误处理移到
onSnapshot的第二个回调参数(onSnapshot的异步错误无法被try/catch捕获)
内容的提问来源于stack exchange,提问作者psych0groov3
相关产品推荐
相关产品推荐

