React三元条件渲染中无法访问auth.currentUser的原因
问题说明
- 开发目标:用户登录状态下展示Sign Out按钮,未登录状态下展示Sign In按钮。
- 现象:初始代码中在登录回调里可以正常获取
auth.currentUser,将按钮渲染改为三元表达式判断后,auth.currentUser取值变为null。
初始可正常打印用户信息的代码如下:
const JoinCard = () => { const auth = getAuth(app); const [user] = useAuthState(auth); const usersRef = collection(db, "users"); const signInWithGoogle = async (e) => { e.preventDefault(); const provider = new GoogleAuthProvider(); signInWithPopup(auth, provider) .then((result) => { console.log(result); console.log(auth.currentUser); }) .catch((error) => { console.log(error); }); const { uid, photoURL, displayName } = auth.currentUser; await addDoc(usersRef, { uid: uid, photoURL: photoURL, displayName: displayName, }); }; return ( <div className="w-[85vw] h-[30vh] m-2 rounded-2xl drop-shadow-xl flex justify-center items-center p-2 bg-white/75"> <button className="sign-in" onClick={signInWithGoogle}> Sign in with Google </button> </div> ); }; function SignOut() { return ( auth.currentUser && ( <button className="sign-out" onClick={() => auth.signOut()}> Sign Out </button> ) ); } export default JoinCard;
调整为三元渲染后出现问题的代码片段:
return ( <div className="w-[85vw] h-[30vh] m-2 rounded-2xl drop-shadow-xl flex justify-center items-center p-2 bg-white/75"> {user ? ( <SignOut /> ) : ( <button className="sign-in" onClick={signInWithGoogle}> Sign in with Google </button> )} </div> ); }; );
触发原因
组件作用域访问错误
你之前的代码里虽然定义了SignOut组件,但初始返回的JSX中根本没有渲染这个组件,组件内部的代码从未执行,所以没有暴露问题。等你加上三元判断,登录后满足渲染条件挂载SignOut时,才会执行组件内部逻辑:SignOut是定义在JoinCard外层的独立函数,而auth是JoinCard内部用const声明的局部变量,外层函数无法访问到这个局部变量,此时SignOut里的auth要么是未定义,要么是全局作用域下初始值为null的auth实例,自然拿不到currentUser。
另外你之前能正常打印auth.currentUser,是因为打印逻辑写在JoinCard组件内部的登录回调里,本身就在auth变量的作用域范围内,和SignOut组件的访问逻辑无关。异步逻辑时序错误(隐藏bug)
你的signInWithGoogle方法里,signInWithPopup是异步Promise,你在then回调外同步执行const { uid, photoURL, displayName } = auth.currentUser时,弹窗登录流程还没走完,此时auth.currentUser本身就是null,就算解决了作用域问题,这里也会抛出解构错误。语法错误
你贴出的修改后返回代码末尾多写了一组);,本身就存在语法闭合问题。
修复方案
- 把
SignOut组件移到JoinCard组件内部定义,让它可以直接访问同作用域下的auth实例;也可以给SignOut新增props传入auth和user状态,不要让子组件依赖父组件未传值的局部变量。 - 把写入Firestore的用户数据操作移到
signInWithPopup的异步回调里,或者加await等待登录流程完成后再读取currentUser取值,不要同步读取异步状态。 - 删除多余的语法闭合符号。
修复后的参考代码:
const JoinCard = () => { const auth = getAuth(app); const [user] = useAuthState(auth); const usersRef = collection(db, "users"); const signInWithGoogle = async (e) => { e.preventDefault(); const provider = new GoogleAuthProvider(); try { // 等待登录流程完成再执行后续操作 const result = await signInWithPopup(auth, provider); console.log(result); const { uid, photoURL, displayName } = auth.currentUser; await addDoc(usersRef, { uid: uid, photoURL: photoURL, displayName: displayName, }); } catch (error) { console.log(error); } }; // SignOut移到组件内部,可直接访问作用域内的auth和user状态 const SignOut = () => { return ( user && ( <button className="sign-out" onClick={() => auth.signOut()}> Sign Out </button> ) ); }; return ( <div className="w-[85vw] h-[30vh] m-2 rounded-2xl drop-shadow-xl flex justify-center items-center p-2 bg-white/75"> {user ? ( <SignOut /> ) : ( <button className="sign-in" onClick={signInWithGoogle}> Sign in with Google </button> )} </div> ); }; export default JoinCard;
内容的提问来源于stack exchange,提问作者JPB
相关产品推荐
相关产品推荐

