Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/django/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
具有可观察redux的Api中间件_Redux_Rxjs_Redux Thunk_Reactivex_Redux Observable - Fatal编程技术网

具有可观察redux的Api中间件

具有可观察redux的Api中间件,redux,rxjs,redux-thunk,reactivex,redux-observable,Redux,Rxjs,Redux Thunk,Reactivex,Redux Observable,我正在重构我的react/redux应用程序,以使用redux observable而不是redux thunk。使用thunk,我设置了一个api中间件,用CALL\u api键监听任何操作,并对数据进行一些操作,准备标题,准备完整url,使用axios执行api调用,以及执行与api调用相关的其他操作分派 重要的是,api中间件发送一个REQUEST\u START操作,该操作为请求提供一个id,并将其状态设置为网络中的挂起。当axios的承诺解决或拒绝时,中间件将发送请求结束操作,更新状态

我正在重构我的react/redux应用程序,以使用
redux observable
而不是
redux thunk
。使用thunk,我设置了一个api中间件,用
CALL\u api
键监听任何操作,并对数据进行一些操作,准备标题,准备完整url,使用
axios
执行api调用,以及执行与api调用相关的其他操作分派

重要的是,api中间件发送一个
REQUEST\u START
操作,该操作为请求提供一个id,并将其状态设置为
网络中的
挂起
。当
axios
的承诺解决或拒绝时,中间件将发送
请求结束
操作,更新状态,以便将当前请求设置为
已解决
已拒绝
。然后将响应返回给最初调度
CALL\u API
操作的调用操作创建者

我还没有弄清楚如何使用
redux observable
来实现这一点。我想要复制的关于上述api中间件的部分是
REQUEST\u START
REQUEST\u END
动作分派。有一个集中的地方处理所有与api调用相关的内容非常方便。我知道我可以在每个执行api调用的epics中有效地调度
REQUEST\u START
REQUEST\u END
操作,但我不想在很多地方重复相同的代码

我创建了一个
apiCallEpic
,监听类型为
CALL\u-API
的操作,并对API调用执行上述设置,从而部分解决了这个问题。然而,一个问题(或者更确切地说,我不喜欢)是,启动api调用的epic(例如
getCurrentUserEpic
)实际上放弃了对
apiCallEpic
的控制

因此,例如,当api调用成功并有响应时,我可能希望在调度要由我的reducer处理的操作之前以某种方式格式化响应数据。也就是说,
getCurrentUserEpic
应该在发送到reducer之前对api调用返回的数据进行一些格式化。通过传递
getCurrentUserEpic
中定义的
payloadHandler
回调函数,
apiCallEpic
可以在获得成功响应时调用该函数,我可以实现类似的功能。然而,我不喜欢这种回调架构,似乎有更好的方法

下面是一些代码,演示了如何使用thunk使用api中间件

import axios from 'axios';


// actionCreators.js

// action types
const CALL_API = "CALL_API";
const FETCH_CURRENT_USER = "FETCH_CURRENT_USER";
const RECEIVE_CURRENT_USER = "RECEIVE_CURRENT_USER";

// action creators for request start and end
export const reqStart = (params = {}) => (dispatch) => {
  const reduxAction = {
    type: REQ_START,
    status: 'pending',
    statusCode: null,
    requestId: params.requestId,
  }
  dispatch(reduxAction);
}

export const reqEnd = (params = {}) => (dispatch) => {
  const {
    requestId,
    response = null,
    error = null,
  } = params;

  let reduxAction = {}

  if (response) {
    reduxAction = {
      type: REQ_END,
      status: 'success',
      statusCode: response.status,
      requestId,
    }
  }
  else if (error) {
    if (error.response) {
      reduxAction = {
        type: REQ_END,
        status: 'failed',
        statusCode: error.response.status,
        requestId,
      }
    }
    else {
      reduxAction = {
        type: REQ_END,
        status: 'failed',
        statusCode: 500,
        requestId,
      }
    }
  }
  dispatch(reduxAction);
}

// some api call to fetch data
export const fetchCurrentUser = (params = {}) => (dispatch) => {
  const config = {
    url: '/current_user',
    method: 'get',
  }

  const apiCall = {
    [CALL_API]: {
      config,
      requestId: FETCH_CURRENT_USER,
    }
  }

  return dispatch(apiCall)
  .then(response => {
    dispatch({
      type: RECEIVE_CURRENT_USER,
      payload: {response},
    })
    return Promise.resolve({response});
  })
  .catch(error => {
    return Promise.reject({error});
  })
}




// apiMiddleware.js

// api endpoint
const API_ENTRY = "https://my-api.com";

// utility functions for request preparation
export const makeFullUrl = (params) => {
  // ...prepend endpoint url with API_ENTRY constant
  return fullUrl
}

export const makeHeaders = (params) => {
  // ...add auth token to headers, etc.
  return headers;
}


export default store => next => action => {
  const call = action[CALL_API];

  if (call === undefined) {
    return next(action);
  }

  const requestId = call.requestId;

  store.dispatch(reqStart({requestId}));

  const config = {
    ...call.config, 
    url: makeFullUrl(call.config),
    headers: makeHeaders(call.config);
  }

  return axios(config)
  .then(response => {
    store.dispatch(reqEnd({
      response,
      requestId,
    }))
    return Promise.resolve(response);
  })
  .catch(error => {
    store.dispatch(reqEnd({
      error,
      requestId,
    }))
    return Promise.reject(error);
  })
}


// reducers.js

// Not included, but you can imagine reducers handle the
// above defined action types and update the state
// accordingly. Most usefully, components can always
// subscribe to specific api calls and check the request
// status.  Showing loading indicators is one
// use case.
下面是我用
redux-observable
实现的代码

export const fetchCurrentUserEpic = (action$, state$) => {
  const requestType = FETCH_CURRENT_USER;
  const successType = RECEIVE_CURRENT_USER;
  const requestConfig = {
    url: "/current_user",
    method: "get",
  }
  const payload = {requestConfig, requestType, successType};
  const payloadNormalizer = ({response}) => {
    return {currentUser: response.data.data};
  }

  return action$.ofType(FETCH_CURRENT_USER).pipe(
    switchMap((action) => of({
      type: CALL_API,
      payload: {...payload, requestId: action.requestId, shouldFail: action.shouldFail, payloadNormalizer},
    })),
  )
}

export const apiEpic = (action$, state$) => {
  return action$.ofType(CALL_API).pipe(
    mergeMap((action) => (
      concat(
        of({type: REQ_START, payload: {requestId: action.payload.requestId, requestType: action.payload.requestType}}),
        from(callApi(action.payload.requestConfig, action.payload.shouldFail)).pipe(
            map(response => {
              return {
                type: action.payload.successType,
                payload: action.payload.payloadNormalizer({response})
              }
            }),
            map(() => {
              return {
                type: REQ_END,
                payload: {status: 'success', requestId: action.payload.requestId, requestType: action.payload.requestType},
              }
           })
          )
        )
      ).pipe(
        catchError(error => {
          console.log('error', error);
          return of({type: REQ_END, payload: {status: 'failed', requestId: action.payload.requestId, requestType: action.payload.requestType}, error});
        })
      )
    )
  )
}
任何意见或建议都将不胜感激

我发现(一个用于构建“获取操作”的库和由redux observable处理的通用史诗)与您在这里试图实现的目标类似(注意,它使用rxjs 5来拯救)。它使用fetch,而不是axios,但很容易替换它。此外,它还具有成功/失败操作的转换器

该库有点陈旧,但克服样板代码的基本思想仍然有效:使用通用epic builder通过调用API获取数据

我是React/Redux/RxJS的新手,但是我看到的
Redux-fetch-epic-builder
的唯一问题是配置
客户端的方法(用axios术语)。也就是说,我不完全满意(因为它不是FSA或RSAA):

但这可能仍然是一种优雅的方式。许可证允许进一步开发该库。我没有将示例从库文档转换为与用户相关的示例,但是使用react observable,当然没有必要引入单独的“api中间件”。(另外,我喜欢
/SUBACTION
\u SUBACTION
更好,但这很容易改变。)

我发现(一个用于构建“获取动作”的库和由redux observable处理的通用史诗)与您在这里试图实现的目标类似(注意,它使用rxjs 5来拯救)。它使用fetch,而不是axios,但很容易替换它。此外,它还具有成功/失败操作的转换器

该库有点陈旧,但克服样板代码的基本思想仍然有效:使用通用epic builder通过调用API获取数据

我是React/Redux/RxJS的新手,但是我看到的
Redux-fetch-epic-builder
的唯一问题是配置
客户端的方法(用axios术语)。也就是说,我不完全满意(因为它不是FSA或RSAA):


但这可能仍然是一种优雅的方式。许可证允许进一步开发该库。我没有将示例从库文档转换为与用户相关的示例,但是使用react observable,当然没有必要引入单独的“api中间件”。(还有,我更喜欢
/SUBACTION
而不是
\u SUBACTION
,但这很容易改变。)

你能发布一些代码来演示你的thunk完成了什么吗?@seniorquico,刚刚用代码更新了问题。很抱歉,我很难想象你想用redux observable做什么,你能把你到目前为止试过的东西贴出来吗?即使它不完整或功能不全,也比从头开始创建它更容易帮助您。这可能是我反对采用redux observable的主要原因之一——它似乎不是一个非常活跃的社区:(我想问题是redux observable约定仍然处于…嗯…变化中。你能发布一些代码来演示你的thunk完成了什么吗?@seniorquico,刚刚用代码更新了问题。对不起,我很难想象你想用redux observable做什么,你能发布你迄今为止尝试过的吗?即使是inco完成或不完全运行比从头开始创建更容易帮助您。这可能是我再次看到的主要原因之一
//action creators
const getComments = (id, page = 1) => ({
    type: GET_COMMENTS,
    host: 'http://myblog.com',
    path: `/posts/${id}/comments`,
    query: {
        page,
    },
})
// ...
const epics = [
    buildEpic(GET_COMMENTS),
]