具有可观察redux的Api中间件
我正在重构我的react/redux应用程序,以使用具有可观察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的承诺解决或拒绝时,中间件将发送请求结束操作,更新状态
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),
]