React应用页面刷新时从Firestore拉取数据如何避免Login组件临时渲染
问题根因
- Redux默认将数据存储在运行内存中,页面刷新后所有状态会重置为初始值,本身不具备自动持久化能力,你认为Redux刷新后会保留数据的认知有误。
- Firebase的
onAuthStateChanged监听是异步触发的,页面刷新后到回调返回用户登录状态的间隔期内,你从Redux拿到的user初始值为null,因此会先渲染Login组件,出现闪烁问题。 useEffect回调中捕获的user是初始渲染时的闭包值,你在dispatch login动作后调用checkDocument(user?.uid)时,user还是初始的null,会导致checkDocument参数错误。
修复方案
1. 新增认证加载状态,避免未完成认证时错误渲染Login
首先修改userSlice.js,新增加载状态标识:
import { createSlice } from "@reduxjs/toolkit"; const initialState = { userDetails: null, // 新增认证加载状态 authLoading: true, }; const userSlice = createSlice({ name: "user", initialState, reducers: { login: (state, action) => { state.userDetails = action.payload; state.authLoading = false; }, logout: (state) => { state.userDetails = null; state.authLoading = false; }, }, }); export const { login, logout } = userSlice.actions; export const selectUser = (state) => state.user.userDetails; export const selectAuthLoading = (state) => state.user.authLoading; export default userSlice.reducer;
2. 修改App.js逻辑,修复闭包问题和渲染逻辑
import { doc, getDoc, setDoc } from "@firebase/firestore"; import React, { useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { Route, Switch } from "react-router"; import Login from "./components/Login"; import Mainthread from "./components/Mainthread"; import Notifications from "./screens/notifications"; import Profile from "./screens/profile"; import { auth, db } from "./services/Firebase"; import { login, logout, selectUser, selectAuthLoading } from "./services/redux/userSlice"; function App() { const user = useSelector(selectUser); const authLoading = useSelector(selectAuthLoading); const dispatch = useDispatch(); async function createDocument(id, data) { await setDoc(doc(db, "users", id), data); } // 直接接收authUser参数,不依赖外部的user变量 async function checkDocument(authUser) { const docRef = doc(db, "users", authUser.uid); const docSnap = await getDoc(docRef); if (docSnap.exists()) { console.log("your document exists with us..."); } else { const data = { email: authUser.email }; createDocument(authUser.uid, data); console.log("your document is being created..."); } } useEffect(() => { // onAuthStateChanged本身返回取消监听的函数,不需要额外包一层 const unsubscribe = auth.onAuthStateChanged(function (authUser) { if (authUser) { dispatch( login({ uid: authUser.uid, email: authUser.email, }) ); // 直接传入回调返回的authUser checkDocument(authUser); } else { dispatch(logout()); console.log("Logged Out!"); } }); // 组件卸载时取消监听,避免内存泄漏 return unsubscribe; }, [dispatch]); // 认证未完成时渲染加载占位,避免闪烁Login if (authLoading) { return <div>加载中...</div> } return ( <> <Switch> <Route path="/notifications"> <Notifications /> </Route> <Route path="/profile"> <Profile /> </Route> <Route path="/">{user ? <Mainthread /> : <Login />}</Route> </Switch> </> ); } export default App;
可选:Redux数据持久化配置
如果需要将Redux中的用户信息持久化到本地存储,避免每次刷新都需要重新拉取额外的用户字段,可以接入redux-persist,修改store配置如下:
import { configureStore } from '@reduxjs/toolkit'; import userReducer from './userSlice'; import storage from 'redux-persist/lib/storage'; import { persistReducer, persistStore } from 'redux-persist'; import { combineReducers } from '@reduxjs/toolkit'; const persistConfig = { key: 'root', storage, whitelist: ['user'] // 只持久化user模块 }; const rootReducer = combineReducers({ user: userReducer }); const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, }), }); export const persistor = persistStore(store);
之后在入口文件用PersistGate包裹App组件即可。
内容的提问来源于stack exchange,提问作者Ujjwal Kirti
相关产品推荐
相关产品推荐

