React导航栏基于Firebase登录状态渲染,避免重复查询Firestore
解决方案
要解决页面切换时重复查询Firestore的问题,核心是仅在用户登录状态变化时触发查询,并缓存已获取的用户信息,避免同一用户重复请求。以下是两种可行方案:
方案一:组件内缓存用户信息
修改Navbar组件的逻辑,利用useRef缓存当前用户邮箱,结合useAuthState的用户状态变化触发查询:
import { useState, useEffect, useRef } from 'react'; import { getAuth, useAuthState } from 'firebase/auth'; import { Link } from 'react-router-dom'; import { getUserDocFromEmail } from './firebaseQuery'; import styles from './Navbar.module.css'; const Navbar = () => { const auth = getAuth(); const [user, authLoading] = useAuthState(auth); const [userInfoLoading, setUserInfoLoading] = useState(false); const [userInfo, setUserInfo] = useState({}); // 缓存当前用户邮箱,避免同一用户重复查询 const currentUserEmailRef = useRef(null); async function fetchUserData(userEmail) { if (!userEmail) return; // 若当前用户已查询过,直接跳过 if (currentUserEmailRef.current === userEmail) return; setUserInfoLoading(true); try { const res = await getUserDocFromEmail(userEmail); setUserInfo(res || {}); currentUserEmailRef.current = userEmail; } catch (err) { console.error('获取用户信息失败:', err); setUserInfo({}); } finally { setUserInfoLoading(false); } } useEffect(() => { // 登出时清空缓存和用户信息 if (!user) { setUserInfo({}); currentUserEmailRef.current = null; return; } // 登录状态变化时查询用户信息 fetchUserData(user.email); }, [user]); // 仅当user状态变化时执行 return ( <div className={styles.authBtns}> {!authLoading && !user ? ( <> <Link to='/login' className={styles.navbtn}>登录</Link> <Link to='/signup' className={styles.navbtn}>注册</Link> </> ) : ( <Link to='/profile' className={styles.welcomeLink}> {!authLoading && user && !userInfoLoading && userInfo.Username ? userInfo.Username : 'Loading...'} </Link> )} </div> ); }; export default Navbar;
方案二:全局Context管理用户状态(推荐)
对于多组件需要用户信息的场景,使用React Context全局缓存用户数据,避免重复查询:
1. 创建UserContext
import { createContext, useContext, useEffect, useState, useRef } from 'react'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; import { getUserDocFromEmail } from './firebaseQuery'; const UserContext = createContext(); export function UserProvider({ children }) { const auth = getAuth(); const [user, setUser] = useState(null); const [userInfo, setUserInfo] = useState({}); const [isLoading, setIsLoading] = useState(true); const currentUserEmailRef = useRef(null); async function fetchUserData(userEmail) { if (!userEmail || currentUserEmailRef.current === userEmail) return; try { const res = await getUserDocFromEmail(userEmail); setUserInfo(res || {}); currentUserEmailRef.current = userEmail; } catch (err) { console.error('获取用户信息失败:', err); setUserInfo({}); } } useEffect(() => { // 监听用户登录状态变化 const unsubscribe = onAuthStateChanged(auth, (authUser) => { setIsLoading(true); if (authUser) { setUser(authUser); fetchUserData(authUser.email); } else { setUser(null); setUserInfo({}); currentUserEmailRef.current = null; } setIsLoading(false); }); return unsubscribe; // 组件卸载时清理监听 }, []); return ( <UserContext.Provider value={{ user, userInfo, isLoading }}> {children} </UserContext.Provider> ); } export function useUser() { return useContext(UserContext); }
2. 在Navbar中使用Context
import { useUser } from './UserContext'; import { Link } from 'react-router-dom'; import styles from './Navbar.module.css'; const Navbar = () => { const { user, userInfo, isLoading } = useUser(); return ( <div className={styles.authBtns}> {!isLoading && !user ? ( <> <Link to='/login' className={styles.navbtn}>登录</Link> <Link to='/signup' className={styles.navbtn}>注册</Link> </> ) : ( <Link to='/profile' className={styles.welcomeLink}> {!isLoading && user && userInfo.Username ? userInfo.Username : 'Loading...'} </Link> )} </div> ); }; export default Navbar;
关键优化点
- 触发时机:仅在用户登录/登出状态变化时查询Firestore,页面切换时若用户状态未变则不重复请求。
- 缓存机制:用
useRef记录已查询的用户邮箱,避免同一用户重复查询。 - 全局共享:Context方案让所有组件共享用户数据,无需各组件单独查询,提升性能。
内容的提问来源于stack exchange,提问作者dsfsd fasdfdsf
相关产品推荐
相关产品推荐

