我正在将我的react / redux应用重构为使用redux-observable
而不是redux-thunk
。使用thunk,我设置了一个api中间件,以使用CALL_API
键侦听任何操作,并对数据进行一些操作,准备标头,准备完整的url,使用axios
执行api调用,以及还可以进行一些与api调用相关的其他动作调度。
重要的是,api中间件调度了一个REQUEST_START
操作,该操作为请求提供了一个ID,并在我状态的pending
部分将其状态设置为network
。当来自axios
的承诺解决或拒绝时,中间件将调度REQUEST_END
动作,更新状态,以便将当前请求设置为resolved
或rejected
。然后,响应将返回给最初分配CALL_API
动作的调用动作创建者。
我无法弄清楚如何使用redux-observable
来做到这一点。我想复制的有关上述api中间件的部分是REQUEST_START
和REQUEST_END
操作调度。拥有一个集中的地方来处理所有与api调用相关的东西是非常方便的。我知道我可以在执行api调用的每个史诗中有效地调度REQUEST_START
和REQUEST_END
动作,但是我不想在很多地方重复相同的代码。
我设法通过创建一个apiCallEpic
来部分解决此问题,该监听器监听类型为CALL_API
的动作,并为api调用进行了上述设置。但是,一个问题(或更确切地说,是我不喜欢的东西)是引发api调用的史诗(例如getCurrentUserEpic
)实际上放弃了对apiCallEpic
的控制。
因此,例如,当api调用成功并且有响应时,我可能希望在分派要由我的reducer处理的操作之前以某种方式格式化该响应数据。也就是说,getCurrentUserEpic
应该对从api调用返回的数据进行某种格式化,然后再发送给reducer。通过传递在payloadHandler
中定义的getCurrentUserEpic
回调函数,{/ {1}}在成功获得响应时可以调用,可以实现接近此目的。但是,我不喜欢这种回调体系结构,而且似乎有一种更好的方法。
这里有一些代码演示了我如何使用thunk来使用api中间件。
apiCallEpic
这是我为使用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
任何评论或建议都值得赞赏!
答案 0 :(得分:0)
我发现redux-fetch-epic-builder(用于构建“提取动作”的库以及由redux-observable处理的通用史诗)与您在此处尝试实现的内容类似(请注意,它使用的是rxjs 5,{{ 3}}进行救援)。它使用fetch而不是axios,但是替换起来很容易。此外,它还具有成功/失败操作的转换器。
该库有些陈旧,但是克服样板代码的基本思想仍然有效:通用的史诗生成器,可以通过对API的调用来获取数据。
我是React / Redux / RxJS的新手,但是我看到的redux-fetch-epic-builder
唯一的问题是配置client
的方式(按axios术语)。也就是说,我不完全满意(因为它不是FSA或RSAA):
//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
好,但是改变并不容易。)