You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

@reduxjs/toolkit中useSelector返回Undefined而非谷歌用户名问题

Redux 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 03:25:05