由于切片之间共享操作,我在 redux 中遇到了循环依赖问题。
在我的 authActions.js 中,我有一个 authFetch 函数,可以在获取时根据需要检查并刷新身份验证。此函数使用 auth reducer,但需要在未与 authSlice 链接的 asyncThunkActions 中使用。
问题是我必须在 authFetch 函数中使用 store.dispatch() ,并且 authActions 文件中 store 的导入会创建循环依赖关系,因为 store 还导入切片文件。
authActions.js
import { store } from "@/redux/store"; // Import your Redux store
import { createAsyncThunk } from "@reduxjs/toolkit";
import Cookies from "js-cookie";
import AppError from "@/utils/AppError";
// async Thunks
export const logIn = createAsyncThunk(
"login",
async (form, { rejectWithValue }) => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/login/`,
{
credentials: "include",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(form),
}
);
console.log(response);
const data = await response.json();
if (!response.ok) {
return rejectWithValue(data);
} else {
return data;
}
} catch (err) {
console.log(err);
return rejectWithValue(err);
}
}
);
export const logOut = createAsyncThunk(
"logOut",
async (_, { rejectWithValue }) => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/logOut`,
{
method: "DELETE",
credentials: "include",
}
);
const data = await response.json();
Cookies.remove("token");
if (!response.ok) {
return rejectWithValue(data);
}
return data;
} catch (err) {
return rejectWithValue(err);
}
}
);
export const getAuth = createAsyncThunk(
"getAuth",
async (_, { rejectWithValue }) => {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/refreshToken/`,
{
credentials: "include",
method: "GET",
}
);
const data = await response.json();
if (!response.ok) {
return rejectWithValue(data);
}
return data;
} catch (err) {
console.log(err);
return rejectWithValue(err);
}
}
);
function authToken() {
return store.getState().auth.data.token;
}
function addAuthTokenToFetchOptions(fetchOptions) {
const fetchOptionsWithAuth = {
...fetchOptions,
headers: {
...(fetchOptions.headers || {}),
Authorization: `Bearer ${authToken()}`,
},
};
return fetchOptionsWithAuth;
}
export const authFetch = async (url, fetchOptions) => {
fetchOptions = addAuthTokenToFetchOptions(fetchOptions);
const response = await fetch(url, fetchOptions);
if (response.ok) {
const data = await response.json();
return data;
} else {
//if response.status 401,403 => invalid access token, try to generate one with the refresh token cookie
if ([401, 403].includes(response.status)) {
const refreshTokenResponse = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/refreshToken`,
{
method: "GET",
credentials: "include",
}
);
//if new access Token is successfully generated => dispatch it in the auth reducers and retry the initial query
if (refreshTokenResponse.ok) {
const credentialsData = await refreshTokenResponse.json();
store.dispatch(setCredentials(credentialsData));
fetchOptions = addAuthTokenToFetchOptions(fetchOptions);
const retryResponse = await fetch(url, fetchOptions);
if (retryResponse.ok) {
data = retryResponse.json();
return data;
} else {
throw new AppError(data);
}
} else {
//if invalid refreshToken inside cookie or no token cookie => user logout
if ([401, 403].includes(refreshTokenResponse.status)) {
store.dispatch(logOut());
}
throw new AppError(data);
}
} else {
throw new AppError(data);
}
}
};
restaurantActions.js 中 authFetch 函数的使用示例
{createAsyncThunk } from "@reduxjs/toolkit";
import { authFetch } from "../auth/authActions";
export const postRestaurantSettings = createAsyncThunk(
"postRestaurantSettings",
async (payload) => {
try {
const data = await authFetch(
`${process.env.NEXT_PUBLIC_API_URL}/restaurants/updateRestaurantSettings`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
}
);
return data;
} catch (error) {
console.error(error);
}
}
);
store.js
import { persistStore, persistReducer } from "redux-persist";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import createWebStorage from "redux-persist/lib/storage/createWebStorage";
import restaurant from "@/redux/restaurant/restaurantSlice";
import cart from "@/redux/cart/cartSlice";
import auth from "@/redux/auth/authSlice";
//workaround (redux-persist failed to create sync storage. falling back to noop storage when you import storage from redux-persist/lib/storage) because you cannot create the local storage in Node.js.
const createNoopStorage = () => {
return {
getItem(_key) {
return Promise.resolve(null);
},
setItem(_key, value) {
return Promise.resolve(value);
},
removeItem(_key) {
return Promise.resolve();
},
};
};
const storage =
typeof window !== "undefined"
? createWebStorage("local")
: createNoopStorage();
const reducers = combineReducers({ restaurant, cart, auth });
const persistConfig = { key: "root", storage, blacklist: ["auth"] };
export const store = configureStore({
reducer: persistReducer(persistConfig, reducers),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ serializableCheck: false }),
});
export const persistor = persistStore(store);
循环依赖错误
ReferenceError: Cannot access '__WEBPACK_DEFAULT_EXPORT__' before initialization
我知道 redux thunk api 可以解决这个问题,但我不想在我的项目中增加设置的复杂性
我能做什么?
提前致谢!
我找到了一种避免导入商店的解决方法,也许不是最好的解决方案,但有效:
getState 和dispatch 可以传递给authFetch 函数,如下所示:
function authToken(getState) {
return getState().auth.data.token;
}
function addAuthTokenToFetchOptions(fetchOptions, getState) {
const fetchOptionsWithAuth = {
...fetchOptions,
headers: {
...(fetchOptions.headers || {}),
Authorization: `Bearer ${authToken(getState)}`,
},
};
return fetchOptionsWithAuth;
}
export const authFetch = async (url, fetchOptions, getState, dispatch) => {
fetchOptions = addAuthTokenToFetchOptions(fetchOptions, getState, dispatch);
const response = await fetch(url, fetchOptions);
if (response.ok) {
const data = await response.json();
return data;
} else {
//if response.status 401,403 => invalid access token, try to generate one with the refresh token cookie
if ([401, 403].includes(response.status)) {
const refreshTokenResponse = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/refreshToken`,
{
method: "GET",
credentials: "include",
}
);
//if new access Token is successfully generated => dispatch it in the auth reducers and retry the initial query
if (refreshTokenResponse.ok) {
const credentialsData = await refreshTokenResponse.json();
dispatch(setCredentials(credentialsData));
fetchOptions = addAuthTokenToFetchOptions(fetchOptions);
const retryResponse = await fetch(url, fetchOptions);
if (retryResponse.ok) {
data = retryResponse.json();
return data;
} else {
throw new AppError(data);
}
} else {
//if invalid refreshToken inside cookie or no token cookie => user logout
if ([401, 403].includes(refreshTokenResponse.status)) {
dispatch(logOut());
}
throw new AppError(data);
}
} else {
throw new AppError(data);
}
}
};
当将调度从 asyncThunkFunction 传递到 authFetch 函数时:
export const postRestaurantSettings = createAsyncThunk(
"postRestaurantSettings",
async (payload, { getState, dispatch }) => {
try {
const data = await authFetch(
`${process.env.NEXT_PUBLIC_API_URL}/restaurants/updateRestaurantSettings`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
},
getState,
dispatch
);
return data;
} catch (error) {
console.error(error);
}
}
);