Redux 循环存储循环依赖

问题描述 投票:0回答:1

由于切片之间共享操作,我在 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 可以解决这个问题,但我不想在我的项目中增加设置的复杂性

我能做什么?

提前致谢!

reactjs redux store circular-dependency
1个回答
0
投票

我找到了一种避免导入商店的解决方法,也许不是最好的解决方案,但有效:

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);
    }
  }
);
© www.soinside.com 2019 - 2024. All rights reserved.