React+Firebase如何高效实现用户名存在性检查?
问题描述
我正在开发一款支持游客通过用户名登录游玩的游戏。原本的实现逻辑为:用户访问网站时,从Firebase获取所有用户数据并赋值给Redux的users状态,当用户点击Play按钮时检查用户名是否已存在。但该方式效率较低,我希望改为点击Play按钮时再获取用户数据。尝试通过async/await实现此逻辑时,出现了检查用户名前users数组始终为空的问题,导致已存在的用户名仍能登录。
使用Firebase获取用户数据的函数实现
import { async } from "@firebase/util"; import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { collection, query, where, getDocs } from "firebase/firestore"; import { db } from '../firebase/auth'; export interface User{ isUser: boolean; id: number; username: string } interface UserState{ users:User[]; isLoading:boolean; errorMessage:string; } export const fetchUsers = createAsyncThunk("users/fetchUsers", async()=>{ const usersArray:User[] = []; const usersRef = collection(db, "users"); const q = query(usersRef, where("isUser", "==", true)); const querySnapshot = await getDocs(q); querySnapshot.forEach(doc =>{ const user = doc.data() as User; usersArray.push(user); }) return usersArray; }) const initialState: UserState = { users:[], isLoading:false, errorMessage:"" } const usersSlice = createSlice({ name:"users", initialState, reducers:{}, extraReducers:(builder)=>{ builder.addCase(fetchUsers.pending, (state, action)=>{ state.isLoading = true; }) builder.addCase(fetchUsers.fulfilled, (state, action)=>{ state.isLoading = false; state.users = action.payload; state.errorMessage = ""; }) builder.addCase(fetchUsers.rejected, (state, action)=>{ state.isLoading = false; state.users = []; if(typeof action.error.message === "string" ){ state.errorMessage = action.error.message; } }) } }) export const usersActions = usersSlice.actions; export const userReducer = usersSlice.reducer
初始渲染时获取数据的原代码
useEffect(() => { dispatch(guestInputActions.changeInputValue(randomUser)); dispatch(fetchUsers()); }, []);
原用户名存在检查逻辑
const handleGuestLogin = () => { async function checkIfUsernameExist() { if (users) { const existUser = users.find((user: User) => user.username === inputValue.toLowerCase()); if (existUser) { dispatch(userLoginActions.setLoginStatus(false)); dispatch(userLoginActions.setErrorMessage("This username already exists!")); return; } else { dispatch(userLoginActions.checkUserName(inputValue)); } } } checkIfUsernameExist(); };
修改后的代码(点击Play时获取数据)
修改后的useEffect代码
useEffect(() => { dispatch(guestInputActions.changeInputValue(randomUser)); // dispatch(fetchUsers()) 注释掉该行 }, [randomUser]);
修改后的登录处理代码
const handleGuestLogin = async () => { async function checkIfUsernameExist() { if (users) { const existUser = users.find((user: User) => user.username === inputValue.toLowerCase()); if (existUser) { dispatch(userLoginActions.setLoginStatus(false)); dispatch(userLoginActions.setErrorMessage("This username already exists!")); return; } else { dispatch(userLoginActions.checkUserName(inputValue)); } } } const val = await dispatch(fetchUsers()); checkIfUsernameExist(); };
问题原因与解决办法
问题原因
调用await dispatch(fetchUsers())后,Redux状态更新是异步的,组件不会立刻重新渲染,此时users还是初始的空数组,导致检查逻辑无法正确判断用户名是否存在。
方案一:直接使用thunk返回值判断
利用dispatch(fetchUsers())返回的Promise,通过unwrap()获取thunk成功后的用户数组,直接用这个数组做检查,无需依赖组件状态:
const handleGuestLogin = async () => { try { // 等待fetchUsers完成,取出返回的用户数组 const users = await dispatch(fetchUsers()).unwrap(); const existUser = users.find((user: User) => user.username === inputValue.toLowerCase()); if (existUser) { dispatch(userLoginActions.setLoginStatus(false)); dispatch(userLoginActions.setErrorMessage("This username already exists!")); return; } dispatch(userLoginActions.checkUserName(inputValue)); } catch (error) { // 处理数据获取失败的情况 dispatch(userLoginActions.setLoginStatus(false)); dispatch(userLoginActions.setErrorMessage("获取用户数据失败,请重试")); } };
方案二:监听状态变化触发检查
如果需要依赖组件的users状态,添加状态标记控制检查时机,通过useEffect监听数据加载完成事件:
import { useState, useEffect } from 'react'; // 组件内添加状态标记是否需要检查用户名 const [shouldCheckUsername, setShouldCheckUsername] = useState(false); useEffect(() => { // 数据加载完成且需要检查时执行逻辑 if (!isLoading && shouldCheckUsername && users) { const existUser = users.find((user: User) => user.username === inputValue.toLowerCase()); if (existUser) { dispatch(userLoginActions.setLoginStatus(false)); dispatch(userLoginActions.setErrorMessage("This username already exists!")); } else { dispatch(userLoginActions.checkUserName(inputValue)); } // 重置标记,避免重复触发 setShouldCheckUsername(false); } }, [users, isLoading, shouldCheckUsername, inputValue, dispatch]); const handleGuestLogin = () => { // 触发数据获取,并标记需要检查用户名 dispatch(fetchUsers()); setShouldCheckUsername(true); };
内容的提问来源于stack exchange,提问作者seniorlearner
相关产品推荐
相关产品推荐

