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

Redux Thunk中间件报错:根Reducer未定义问题求助

Redux整合MSAL时"Expected root reducer to be a function"错误排查

我在应用中使用MSAL-React实现OAuth(微软身份验证以启用SSO),希望将MSAL获取的accessToken存入Redux实现跨页面访问,但每次需刷新多次才能从Redux Store获取token。尝试用Redux Thunk中间件解决时,持续报错:

WebpackError: Expected the root reducer to be a function. Instead, received: 'undefined'

问题代码

store.js

import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './rootReducer'

const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware))

const store = createStore(rootReducer, composedEnhancer)
export default store

rootReducer.js

import { fetchUsersToken, reducer } from "./reducer";
import { combineReducers } from "redux";

// 之前写法是export const rootReducer = combineReducers({...})
export const rootReducer = () => combineReducers({ 
    reduxState: reducer
});

reducer.js

import { useMsal } from "@azure/msal-react";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { loginRequest } from "./authConfig";
import { msalInstance } from "./pages";
import * as actionTypes from "./action-types";

const initialState = []

export default function reducer(state = initialState, action) {
  switch (action.type) {
// omit other reducer cases
case actionTypes.GETTOKEN: {
  return action.payload
}
default:
  return state
}
}

export const fetchUsersToken = createAsyncThunk("users/fetchUsersToken", async 
(dispatch, getState) => {
 const {accounts} = useMsal()
 const request = {
 ...loginRequest,
 account: accounts[0],
 }
 const tkn = await msalInstance.acquireTokenSilent(request)
 dispatch({ type: actionTypes.GETTOKEN, payload: tkn.accessToken })
});

index.js

import React from "react"
import { Helmet } from "react-helmet"
import { PublicClientApplication } from "@azure/msal-browser"
import { MsalProvider } from "@azure/msal-react"
import { msalConfig } from "../authConfig"
import PageLayout from "./PageLayout"
import { createTheme, ThemeProvider } from "@mui/material/styles"

//Redux
 import { Provider } from "react-redux";
 import {store} from "../store";

 store.dispatch(fetchUsersToken());

 //Redux Ends here
 export const msalInstance = new PublicClientApplication(msalConfig)

 const theme = createTheme({
  palette: {
   primary: { main: "#000000" },
   },
   })

 export default function Home() {
   return (
    <>
  <Helmet>
    <title>XXXXXXXXXX</title>
  </Helmet>
  <MsalProvider instance={msalInstance}>
    <ThemeProvider theme={theme}>
    <Provider store={store}>
      <PageLayout />
      </Provider>
    </ThemeProvider>
  </MsalProvider>
</>
 )
}

问题根源及修复方案

1. RootReducer定义错误

createStore要求传入的rootReducer必须是一个函数,但你的代码将rootReducer定义为返回combineReducers结果的函数,导致传入createStore时实际是一个未执行的函数引用,而非真正的reducer函数。

修复后的rootReducer.js:

import { reducer } from "./reducer";
import { combineReducers } from "redux";

// 直接导出combineReducers的结果,而非包裹在函数中
export const rootReducer = combineReducers({ 
  reduxState: reducer
});

2. AsyncThunk写法错误

  • createAsyncThunk的回调函数参数格式错误:第一个参数是传入的payload,第二个是包含dispatch/getState的thunkAPI对象,而非直接传入dispatch和getState
  • 不能在非React组件/自定义Hook中使用useMsal,需直接通过msalInstance获取账户信息
  • createAsyncThunk会自动分发pending/fulfilled/rejected类型的action,无需手动dispatch自定义action

修复后的reducer.js:

import { createAsyncThunk } from "@reduxjs/toolkit";
import { loginRequest } from "./authConfig";
import { msalInstance } from "./pages";
import * as actionTypes from "./action-types";

// token是字符串,初始值改为空字符串更合理
const initialState = "";

export default function reducer(state = initialState, action) {
  switch (action.type) {
    case actionTypes.GETTOKEN: {
      return action.payload;
    }
    // 可选:处理createAsyncThunk自动生成的fulfilled action
    case "users/fetchUsersToken/fulfilled": {
      return action.payload;
    }
    default:
      return state;
  }
}

export const fetchUsersToken = createAsyncThunk("users/fetchUsersToken", async (_, thunkAPI) => {
  const accounts = msalInstance.getAllAccounts();
  if (!accounts.length) {
    throw new Error("未找到登录账户");
  }
  const request = {
    ...loginRequest,
    account: accounts[0],
  };
  const tkn = await msalInstance.acquireTokenSilent(request);
  // 返回token,createAsyncThunk会自动分发fulfilled action
  return tkn.accessToken;
});

3. Index.js中的初始化错误

  • store导入方式错误:store.js是export default,需用import store from "../store"而非import {store}
  • dispatch时机错误:msalInstance在dispatch之后才创建,会导致msalInstance为undefined;且不能在组件外部直接调用store.dispatch,需在组件内部通过useDispatch和useEffect触发
  • 将token获取逻辑封装在组件内部,确保MSAL实例已初始化

修复后的index.js:

import React, { useEffect } from "react"
import { Helmet } from "react-helmet"
import { PublicClientApplication } from "@azure/msal-browser"
import { MsalProvider } from "@azure/msal-react"
import { msalConfig } from "../authConfig"
import PageLayout from "./PageLayout"
import { createTheme, ThemeProvider } from "@mui/material/styles"

//Redux
import { Provider, useDispatch } from "react-redux";
import store from "../store";
import { fetchUsersToken } from "../reducer"; // 导入fetchUsersToken

// 先初始化MSAL实例
export const msalInstance = new PublicClientApplication(msalConfig)

const theme = createTheme({
  palette: {
    primary: { main: "#000000" },
  },
})

// 封装组件处理token获取
const AppWrapper = () => {
  const dispatch = useDispatch();

  useEffect(() => {
    // 组件挂载后触发token获取
    dispatch(fetchUsersToken()).catch(err => {
      console.error("获取token失败:", err);
      // 可选:处理token获取失败逻辑,比如跳转到登录页
    });
  }, [dispatch]);

  return <PageLayout />;
}

export default function Home() {
  return (
    <>
      <Helmet>
        <title>XXXXXXXXXX</title>
      </Helmet>
      <MsalProvider instance={msalInstance}>
        <ThemeProvider theme={theme}>
          <Provider store={store}>
            <AppWrapper />
          </Provider>
        </ThemeProvider>
      </MsalProvider>
    </>
  )
}

关键修复总结

  1. 修正rootReducer的导出方式,直接返回combineReducers结果
  2. 调整AsyncThunk的参数结构,移除React Hook的不当使用
  3. 确保MSAL实例先于Redux dispatch初始化,在组件内部通过useEffect触发token获取
  4. 适配reducer初始状态类型为字符串,匹配token的数据格式

内容的提问来源于stack exchange,提问作者Lakshmi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 04:20:14