@reduxjs/toolkit中useSelector返回Undefined而非谷歌用户名问题
问题描述
使用@reduxjs/toolkit开发时,在AuthContext.js中通过useSelector调用userSlice.js里的selectUserName(原逻辑(state) => state.user.name)获取谷歌用户名,返回值为Undefined。修改为(state) => state.user.userState.name后能正确获取,寻求进一步优化方案。
相关代码
store/index.js
import { configureStore } from "@reduxjs/toolkit"; import rootReducer from "../reducers/index"; const store = configureStore({ reducer: { user: rootReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, }), }); export default store;
reducers/index.js
import { combineReducers } from "redux"; import userReducer from "./userSlice"; const rootReducer = combineReducers({ userState: userReducer, }); export default rootReducer;
userSlice.js
import { createSlice } from "@reduxjs/toolkit"; export const INITIAL_STATE = { name: "Hello World", email: "", photo: "", }; export const userSlice = createSlice({ name: "user", initialState: INITIAL_STATE, reducers: { setUserLoginDetails: (state, action) => { console.log("Initial", state.name); state.name = action.payload.name; state.email = action.payload.email; state.photo = action.payload.photo; console.log("Updated", state.name); }, setSignOutState: (state) => { state.name = null; state.email = null; state.photo = null; }, }, }); export const { setUserLoginDetails, setSignOutState } = userSlice.actions; // 修改前:返回Undefined // export const selectUserName = (state) => state.user.name; // 修改后:可正确获取 export const selectUserName = (state) => state.user.userState.name; export const selectUserEmail = (state) => state.user.userState.email; export const selectUserPhoto = (state) => state.user.userState.photo; export default userSlice.reducer;
AuthContext.js
import React, { useContext, createContext, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { GoogleAuthProvider, signInWithPopup, signInWithRedirect, signOut, onAuthStateChanged, } from "firebase/auth"; import { auth } from "../firebase"; import { setUserLoginDetails, selectUserName } from "../reducers/userSlice"; import store from "../store/index"; const AuthContext = createContext(); export const AuthContextProvider = ({ children }) => { const dispatch = useDispatch(); const username = useSelector(selectUserName); const googleSignIn = () => { const provider = new GoogleAuthProvider(); signInWithPopup(auth, provider) .then((result) => { console.log("Check Initial Store", store.getState()); console.log("Check Initial Name", username); setUser(result.user); console.log("Check Updated Store", store.getState()); console.log("Check Updated Name", username); }) .catch((e) => { const eCode = e.code; const eMessage = e.message; const eEmail = e.email; const eCredential = e.credential; }); }; const setUser = (user) => { dispatch( setUserLoginDetails({ name: user.displayName, email: user.email, photo: user.photoURL, }) ); }; return ( <AuthContext.Provider value={{ googleSignIn, username }}> {children} </AuthContext.Provider> ); }; export default AuthContext; export const UserAuth = () => { return useContext(AuthContext); };
App.js
import { BrowserRouter as Router, Route, Routes, Navigate, } from "react-router-dom"; import "./App.css"; import Header from "./components/Header"; import Home from "./components/Home"; import Login from "./components/Login"; import { AuthContextProvider, UserAuth } from "./context/AuthContext"; import { useEffect } from "react"; import { onAuthStateChanged } from "firebase/auth"; function App(props) { const { username } = UserAuth(); useEffect(() => { console.log("User name is", username); }, [username]); return ( <div className="App"> <AuthContextProvider> <Router> <Routes> <Route exact path="/" element={<Login />} /> <Route path="/home" element={ <> <Header /> <Home /> </> } /> </Routes> </Router> </AuthContextProvider> </div> ); } export default App;
优化建议
1. 简化Reducer层级,消除冗余嵌套
当前store的状态结构是state.user.userState.xxx,属于不必要的多层嵌套。可以直接在configureStore中注册userSlice的reducer,无需额外通过combineReducers包裹:
修改store/index.js
import { configureStore } from "@reduxjs/toolkit"; import userReducer from "../reducers/userSlice"; const store = configureStore({ reducer: { // 直接注册userReducer,移除rootReducer的冗余包裹 user: userReducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, }), }); export default store;
删除reducers/index.js(如果没有其他需要合并的reducer),此时selector可以恢复最初的简洁写法:
export const selectUserName = (state) => state.user.name; export const selectUserEmail = (state) => state.user.email; export const selectUserPhoto = (state) => state.user.photo;
若后续需要添加更多reducer,再用combineReducers合并,但每个slice的reducer直接作为顶层属性即可,避免多层嵌套。
2. 用记忆化Selector优化性能(可选)
如果组件频繁调用这些Selector,或者Selector依赖多个状态,可使用Redux Toolkit内置的createSelector(来自reselect库)创建记忆化Selector,避免不必要的重复计算:
import { createSlice, createSelector } from "@reduxjs/toolkit"; // ... slice定义部分 ... // 基础Selector:获取整个user状态 const selectUserState = (state) => state.user; // 记忆化Selector:仅当user状态变化时才重新计算 export const selectUserName = createSelector( [selectUserState], (user) => user.name ); export const selectUserEmail = createSelector( [selectUserState], (user) => user.email ); export const selectUserPhoto = createSelector( [selectUserState], (user) => user.photo );
3. 避免直接依赖store实例,用useSelector订阅状态
当前googleSignIn中直接调用store.getState(),虽能获取最新状态,但更推荐用useSelector订阅状态变化。可在AuthContextProvider中通过useEffect监听username变化来打印日志,避免闭包捕获旧值的问题:
// AuthContext.js中修改googleSignIn const googleSignIn = () => { const provider = new GoogleAuthProvider(); signInWithPopup(auth, provider) .then((result) => { setUser(result.user); }) .catch((e) => { // 错误处理逻辑 }); }; // 添加useEffect监听状态变化 useEffect(() => { console.log("Updated Username:", username); }, [username]);
4. 统一初始状态,避免逻辑混淆
当前INITIAL_STATE的name设为"Hello World",登录后替换为用户名、登出后设为null,状态值类型不一致。建议统一初始状态为空值null,保持状态一致性:
export const INITIAL_STATE = { name: null, email: null, photo: null, };
这样判断用户是否登录时,直接检查username !== null即可,避免初始默认值造成的逻辑混淆。
内容的提问来源于stack exchange,提问作者LimeDev

