Next13使用createAsyncThunk时出现初始化前访问slice错误
解决Next.js中Redux异步Thunk的初始化引用错误
问题背景
我需要从后端获取用户数据并存入Redux,因此使用createAsyncThunk实现了getUserData函数,在slice中配置了extraReducers处理异步状态。但在登录流程中,获取到后端返回的token后dispatch该函数时,执行npm run build出现预渲染报错,且不同页面/layout中dispatch的报错情况不一致。
具体报错情况
- 登录页面dispatch:执行
npm run build时提示:预渲染页面"/login"时出错
ReferenceError: Cannot access 'slice' before initialization - 主页面dispatch:build时报错
ReferenceError: Cannot access 'user_slice' before initialization(已通过next.config.ts设置从"/"重定向到其他页面,未实际使用该页面) - 主layout/其他layout中dispatch:浏览器直接报错
Error: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization,错误指向store文件中引入的userSlice - shop页面dispatch:可正常运行,但不符合业务逻辑
相关代码片段
Store代码
import { combineReducers } from 'redux'; import { configureStore } from '@reduxjs/toolkit'; import { persistStore, persistReducer } from 'redux-persist'; import createWebStorage from 'redux-persist/lib/storage/createWebStorage'; import authSlice from './auth/slice'; import modalSlice from './modal/slice'; import userSlice from './user/slice'; import currencySlice from './currency/slice'; const createNoopStorage = () => { return { getItem(_key: any) { return Promise.resolve(null); }, setItem(_key: any, value: any) { return Promise.resolve(value); }, removeItem(_key: any) { return Promise.resolve(); }, }; }; const storage = typeof window !== 'undefined' ? createWebStorage('local') : createNoopStorage(); const rootReducer = combineReducers({ authSlice, modalSlice, userSlice, currencySlice, }); const persistConfig = { key: 'root', storage, whitelist: ['authSlice', 'userSlice'], }; const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, }), }); export const persistor = persistStore(store); export type RootState = ReturnType<typeof rootReducer>; export type AppDispatch = ReturnType<typeof store.dispatch>;
Slice代码
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { initialState } from './initialsState'; import { get } from '@/helpers/utils/fetch'; export const getUserData = createAsyncThunk('user/userData', async () => { try { return await get({ url: '/api/user' }); } catch (error) { console.log('thunk user data error: ', error); } }); const userSlice = createSlice({ name: 'user', initialState, reducers: { clearUserData: (state) => { state.user = null; }, }, extraReducers: (builder) => { builder .addCase(getUserData.pending, (state) => { state.status = 'loading'; }) .addCase(getUserData.fulfilled, (state, action) => { state.status = 'success'; state.user = action.payload; }) .addCase(getUserData.rejected, (state, action) => { state.status = 'failed'; state.error = action.error.message; }); }, }); export const { clearUserData } = userSlice.actions; export default userSlice.reducer;
登录函数代码
const onLogin = async () => { setIsLoading(true); try { const response = await axiosInstance.post('/api/auth/login', userData); const token = response.data.accessTokens; dispatch(setToken(token)); (dispatch as ThunkDispatch<RootState, void, AnyAction>)(getUserData()); push(routes.shop); setError(''); setIsLoading(false); } catch (error: any) { setIsLoading(false); if (error.response && error.response.status === 400) { setError('Incorrect credentials'); } else { console.log('Login Error', error); } } };
解决方案
核心问题是循环引用或预渲染阶段提前执行了依赖未初始化模块的代码,可通过以下方式解决:
1. 排查并消除循环引用
- 检查
@/helpers/utils/fetch是否直接导入了store或authSlice来获取token,若如此,改为手动在请求头传入token(从登录返回的token中获取),避免工具函数依赖Redux store。 - 确认组件是否同时导入了store和slice导出的函数,导致store初始化前就引用了slice模块。
2. 延迟执行异步操作(避免预渲染阶段触发)
在登录组件或layout中,使用useEffect包裹dispatch逻辑,确保仅在客户端渲染时执行:
import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { getUserData } from '../path/to/user/slice'; // 登录组件内 useEffect(() => { // 仅在客户端且存在token时执行 if (typeof window !== 'undefined' && token) { dispatch(getUserData()); } }, [dispatch, token]);
3. 使用类型化Dispatch替代手动断言
在组件中使用Redux提供的类型化dispatch,避免手动类型断言导致的提前引用:
import { useDispatch } from 'react-redux'; import type { AppDispatch } from '../path/to/store'; import { getUserData } from '../path/to/user/slice'; const dispatch = useDispatch<AppDispatch>(); // 直接调用,无需类型断言 dispatch(getUserData());
4. 动态导入Thunk函数
在登录函数中动态导入getUserData,避免组件初始化阶段就加载依赖模块:
const onLogin = async () => { setIsLoading(true); try { const response = await axiosInstance.post('/api/auth/login', userData); const token = response.data.accessTokens; dispatch(setToken(token)); // 动态导入Thunk const { getUserData } = await import('../path/to/user/slice'); dispatch(getUserData()); push(routes.shop); setError(''); setIsLoading(false); } catch (error: any) { // 错误处理逻辑 } };
5. 调整Store初始化逻辑
确保所有slice模块在store初始化前完成加载,可将store的初始化逻辑延迟到客户端,比如在_app.tsx中动态导入store(仅Next.js Pages Router适用)。
内容的提问来源于stack exchange,提问作者Oliver
相关产品推荐
相关产品推荐

