首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >具有redux-observable的Api中间件

具有redux-observable的Api中间件
EN

Stack Overflow用户
提问于 2019-04-27 04:03:18
回答 1查看 685关注 0票数 1

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

重要的是,api中间件分派一个REQUEST_START操作,该操作为请求提供一个id,并在我的状态的network部分中将其状态设置为pending。当来自axios的promise解析或拒绝时,中间件分派一个REQUEST_END操作,更新状态,以便将当前请求设置为resolvedrejected。然后将响应返回给最初分派CALL_API操作的调用操作创建者。

我还不知道如何使用redux-observable来做到这一点。关于上面描述的api中间件,我想复制的部分是REQUEST_STARTREQUEST_END操作分派。有一个集中的地方来处理所有与api调用相关的事情是非常方便的。我知道我可以有效地在执行REQUEST_START调用的每个epics中分派REQUEST_END和api操作,但我不想在许多地方重复相同的代码。

我设法部分地解决了这个问题,方法是创建一个侦听类型为CALL_API的操作并为apiCallEpic调用执行上述设置的api。然而,一个问题(或者更确切地说,我不喜欢的事情)是,发起api调用的epic (例如getCurrentUserEpic)本质上将控制权交给了apiCallEpic

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

这里有一些代码演示了我如何使用thunk来使用api中间件。

代码语言:javascript
复制
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实现类似功能的代码。

代码语言:javascript
复制
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});
        })
      )
    )
  )
}

任何意见或建议,欢迎光临!

EN

回答 1

Stack Overflow用户

发布于 2019-12-07 20:55:26

我发现redux-fetch-epic-builder (一个用于构建"fetch actions“和由redux-observable处理的通用epics的库)与您在这里尝试实现的目标类似(注意它使用了rxjs5,this guide来拯救)。它使用的是fetch,而不是axios,但它很容易被替换。此外,它还具有用于成功/失败操作的转换器。

这个库有点老了,但是克服样板代码的基本思想仍然有效:泛型epic-builder通过调用API来获取数据。

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

代码语言:javascript
复制
//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),
]

但这可能仍然是一种优雅的方式。并且许可证允许进一步开发该库。我没有将示例从库文档转换为与用户相关的示例,但是使用react-observable肯定不需要引入单独的"api中间件“。(此外,与_SUBACTION相比,我更喜欢/SUBACTION,但是更改它是微不足道的。)

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55874276

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档