首页 > 解决方案 > 如何避免在使用 easy-peasy redux 库时导入商店时发生的循环依赖?

问题描述

创建商店后导入商店会导致此问题。

store.ts

/**
 * Author: Rahul Shetty
 *
 * The central redux store of our app is created and exported to be used from
 * here.
 */
import { createStore, persist } from 'easy-peasy';
import { services } from '@services/index';
import storage from '@utils/storage';
import { Entities } from 'types/entities';
import storeModel from '@models/index';

// Add any additional store enhancers
let storeEnhancers: any[] = [];

if (__DEV__) {
  const reactotron = require('./reactotron.config').default;
  // @types/ws
  const reactotronConfig = reactotron();

  // Global variable. Use it to log your variable and you can see the result in reactotrons
  (global as any).tronlog = (value: any) => reactotronConfig.log('TRON', value);
  storeEnhancers = [...storeEnhancers, reactotronConfig.createEnhancer()];
}

export const store = createStore(
  persist(storeModel, {
    whitelist: [Entities.LANGUAGES],
    storage,
  }),
  {
    injections: { ...services },
    enhancers: [...storeEnhancers],
  },
); //  create our store

if (__DEV__) {
  // @types/webpack-env
  if (module.hot) {
    // At times the app breaks. Just reload and start again
    module.hot.accept('../models', () => {
      store.reconfigure(storeModel); //  Here is the magic
    });
  }
}

export default store;

上面代码片段中要注意的重要行是injections: { ...services }. 它将所有服务作为用于进行 API 调用的函数注入。

服务/index.ts

import * as placesServices from './places';
import * as appointmentServices from './appointment';

export const services = {
  placesServices,
  appointmentServices,
};

服务/places.ts

import { PlaceServices } from 'types/places';
import { customerAPIInstance } from './api';

export const getPlaces: PlaceServices['getPlaces'] = () =>
  customerAPIInstance.get('/branches');

export const favoritePlace: PlaceServices['favoritePlace'] = (info) =>
  customerAPIInstance.put('/favorite', info);

下面给出的代码片段创建了一个依赖循环。

服务/api.ts

/**
 * Author: Rahul Shetty
 *
 * API Wrapper for the app
 */
import Config from 'react-native-config';
import axios, { Method, AxiosInstance } from 'axios';
import { Entity } from 'types/entities';
import store from '@store/index';
import { ActionCreator } from 'easy-peasy';
import { MetaPayload } from 'types/meta';

type StoreOptions = {
  setPending: ActionCreator<MetaPayload>;
  setError: ActionCreator<MetaPayload>;
  resetError: ActionCreator<MetaPayload>;
};

type APIOptions = {
  method: Method;
  url: string;
  data: DynamicObject;
  entity?: Entity;
};

const { BASE_URL } = Config;

export const customerAPIInstance = axios.create({
  baseURL: BASE_URL,
  timeout: 20000,
  headers: { 'Content-Type': 'application/json' },
});

export const apiConfig = (
  apiInstance: AxiosInstance,
  storeOptions: StoreOptions,
) => async <T>(apiOptions: APIOptions): Promise<T> => {
  const { method, url, data, entity } = apiOptions;
  const { setPending, setError, resetError } = storeOptions;

  /**
   * if the developer doesn't wanna track the asynchronous states, then we avoid
   * calling store actions by not passing the entity name
   */
  if (entity) {
    setPending({
      pending: true,
      entity,
    });
  }

  try {
    const result = await apiInstance({
      method,
      url,
      data,
    });

    // Reset any error if the API call was successful
    if (entity) {
      resetError({
        entity,
      });
    }

    return result.data;
  } catch (err) {
    const message =
      err.response && err.response.data && err.response.data.message
        ? err.response.data.message
        : err.message;

    // Save the error related data if the API call was unsuccessful
    if (entity) {
      setError({
        error: {
          message,
          statusCode: err.status || 500,
        },
        entity,
      });
    }

    throw Error(err);
  } finally {
    // The API call has either successfully resolved or has been rejected.
    // In either case, pending should be set to false
    if (entity) {
      setPending({
        pending: false,
        entity,
      });
    }
  }
};

export const CustomerAPI = apiConfig(customerAPIInstance, {
  setPending: store.getActions().metadata.setPending,
  setError: store.getActions().metadata.setError,
  resetError: store.getActions().metadata.resetError,
});

正如您可能在上面可用的代码片段中观察到的那样,我正在尝试使用可通过商店获得的简单易用的redux 操作从单点处理错误和加载状态。

具体来说,import store from '@store/index';创建依赖循环。

但是,由于注入只不过是反过来使用存储的服务,因此形成了依赖循环。

存储 -> 注入 -> 服务 -> 地点 -> API 实例 -> 存储

我确实有解决办法。我可以通过调用服务的方法传递动作。例如,

模型/地点.ts

const placesModel = {
  fetchPlaces: thunk(async(actions, payload, { injections, getStoreActions }) => {
    injections.getPlaces(payload, getStoreActions);
  })
};

但是,使用上面显示的方法,我必须继续将存储操作作为第二个参数传递给所有服务。

如何通过共享存储操作来打破依赖循环,以确保 API 实例可以从单个位置设置错误和加载状态?

标签: javascriptreactjsreact-nativereduxeasy-peasy

解决方案


这里的主要问题是导入store.ts文件,因为它依赖于服务。我进行了以下更改以解决此问题。

我创建async-handler.ts并添加了以下代码:

import { Entity } from 'types/entities';
import type { Actions, Meta, State, Dispatch } from 'easy-peasy';
import { Services } from 'types/services';
import { StoreModel } from 'types/model';

// @TODO: Simplify the repetitive types
type HandleAsyncStates = <Model extends object = {}, Payload = void>(
  callback: (
    actions: Actions<Model>,
    payload: Payload,
    helpers: {
      dispatch: Dispatch<StoreModel>;
      getState: () => State<Model>;
      getStoreActions: () => Actions<StoreModel>;
      getStoreState: () => State<StoreModel>;
      injections: Services;
      meta: Meta;
    },
  ) => Promise<any>,
) => (
  actions: Actions<Model>,
  payload: Payload,
  helpers: {
    dispatch: Dispatch<StoreModel>;
    getState: () => State<Model>;
    getStoreActions: () => Actions<StoreModel>;
    getStoreState: () => State<StoreModel>;
    injections: Services;
    meta: Meta;
  },
) => Promise<any>;

export const handleAsyncStates: HandleAsyncStates = (callback) => async (
  actions,
  payload,
  helpers,
) => {
  const { getStoreActions, meta } = helpers;
  const { setPending, resetError, setError } = getStoreActions().metadata;

  const entity = meta.path[0] as Entity;

  // Start the pendinng state
  setPending({
    pending: true,
    entity,
  });

  try {
    // Perform you API or async tasks inside the callback, and return the promise
    const result = await callback(actions, payload, helpers);

    // reset any error if it has been set
    resetError(entity);

    return result;
  } catch (err) {
    const message =
      err.response && err.response.data && err.response.data.message
        ? err.response.data.message
        : err.message;

    const errData = {
      message,
      statusCode: err.status || 500,
    };

    // Save the error related data in the store if the API call was unsuccessful
    setError({
      error: errData,
      entity,
    });

    return errData;
  } finally {
    // The API call has either successfully resolved or has been rejected.
    // In either case, pending should be set to false
    setPending({
      pending: false,
      entity,
    });
  }
};

这个充当 HOC 的异步处理程序然后被传递给存储 thunk,如下所示:

  fetchAndSaveNearbyPlaces: thunk(
    handleAsyncStates<PlacesModel, PlaceAPIOption>(
      async (actions, payload, { injections }) => {
        const { placesServices } = injections;
        const response = await placesServices.getPlaces<PlaceListResponse>();

        actions.saveNearbyPlaces(response.data.result);

        return response;
      },
    ),
  ),

存储 thunk 提供了执行常见存储操作(例如跟踪 API 异步状态)所需的必要操作和服务,并且完全无需导入store.ts


推荐阅读