ReactJS中Firebase onAuthStateChanged触发两次的问题咨询
问题分析与修复方案
你的onAuthStateChanged触发两次,核心是代码里的订阅管理逻辑错误,具体问题如下:
1. 提前取消订阅的错误操作
你在onAuthStateChanged的回调开头就调用了unsubscribe(),但Firebase Auth的状态监听机制是:初始化时会先返回一个临时的null状态(表示身份还在确认中),等本地缓存的用户信息加载完成后,会再次触发回调返回用户对象。这种提前取消订阅的操作不仅打断了正常的状态确认流程,还可能因为Firebase内部的调度逻辑,导致两次回调都被执行后才完成取消,最终出现重复触发的现象。
2. 异常捕获范围错误
外层的try/catch只能捕获onAuthStateChanged订阅时的同步错误,无法捕获回调内部异步操作(比如getIdToken(true))的异常,导致认证错误无法被正确捕获和处理。
3. 状态更新时序混乱
getIdToken(true)是异步操作,在未完成前没有状态锁定,若回调重复触发,会导致多次异步请求同时进行,进一步引发状态更新冲突。
修复后的代码
import React, { useEffect, useState } from 'react'; import { getAuth, onAuthStateChanged, User } from 'firebase/auth'; import { useNavigate } from 'react-router-dom'; import { RiseLoader } from 'react-spinners'; import ErrorPage from '../Pages/ErrorPage'; export interface IAuthRouteProps { children: React.ReactNode; onTokenReceived: (token: string) => void; } const AuthRoute: React.FunctionComponent<IAuthRouteProps> = ({ children, onTokenReceived }) => { const auth = getAuth(); const navigate = useNavigate(); const [loading, setLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(""); useEffect(() => { setErrorMessage(""); // 创建订阅并保存取消函数 const unsubscribe = onAuthStateChanged(auth, async (user: User | null) => { try { if (user) { console.log('triggering routes -@c1'); const token = await user.getIdToken(true); setLoading(false); onTokenReceived(token); } else { console.log('unauthorized'); setLoading(false); navigate('/login'); } } catch (error) { setErrorMessage("Authentication issue"); setLoading(false); } }); // 组件卸载时取消订阅,避免内存泄漏和重复触发 return () => unsubscribe(); }, []); if (loading) return <div className="mainHome"> <RiseLoader className="progressStyle" color="#5AA5F5" size={16} /> {errorMessage.length > 1 ? <ErrorPage message={''} code={0} /> :<></>} </div>; return <>{children}</>; }; export default AuthRoute;
关键修复说明
- 正确管理订阅生命周期:移除回调内的
unsubscribe(),改为在useEffect的清理函数中执行,确保组件卸载时正确取消监听,同时让Firebase完成完整的身份确认流程。 - 调整异常捕获位置:将
try/catch移到回调内部,捕获异步操作的错误,保证错误能被及时处理。 - 统一状态更新:无论用户是否登录,都在逻辑执行完毕后设置
loading为false,避免页面长时间处于加载状态。
内容的提问来源于stack exchange,提问作者Prasath S
相关产品推荐
相关产品推荐

