Asynchronous Redux:使用异步中间件与在成功函数上调度操作
我正在尝试将Redux集成到我的React项目中。 目前我没有使用任何通量框架 我的应用程序从API获取一些数据,并以漂亮的方式显示,如下所示:Asynchronous Redux:使用异步中间件与在成功函数上调度操作,asynchronous,reactjs,redux,redux-thunk,Asynchronous,Reactjs,Redux,Redux Thunk,我正在尝试将Redux集成到我的React项目中。 目前我没有使用任何通量框架 我的应用程序从API获取一些数据,并以漂亮的方式显示,如下所示: componentDidMount() { getData(); } getData() { const self = this; ajax({ url: apiUrl, }) .success(function(data) { self.setState({ data: data, });
componentDidMount() {
getData();
}
getData() {
const self = this;
ajax({
url: apiUrl,
})
.success(function(data) {
self.setState({
data: data,
});
})
.error(function() {
throw new Error('Server response failed.');
});
}
在阅读关于Redux的文章时,我已经确定了两种可能的方法,可以用来处理在商店中存储成功数据的问题:
- 使用异步中间件,或
- 从ajax函数的成功回调中分派操作
添加_数据
回调中的调度操作听起来很容易实现和理解,而异步中间件则很难向不习惯使用函数式语言的人解释 我使用
redux-thunk
进行ajax调用,并使用redux-promise
处理如下所示的承诺
function getData() { // This is the thunk creator
return function (dispatch) { // thunk function
dispatch(requestData()); // first set the state to 'requesting'
return dispatch(
receiveData( // action creator that receives promise
webapi.getData() // makes ajax call and return promise
)
);
};
}
对于初次使用回调的人来说,在回调中调度操作似乎更容易理解,但使用中间件有以下优点:
- thunks允许分派多个操作(如上例所示)-- 首先将状态设置为“请求”,可由加载指示器使用, )
- 它允许有条件地分派其他操作。例如,仅当自上次提取后的时间超过阈值时提取
- 您仍然可以在没有中间件的情况下实现所有这些,但使用中间件可以帮助您在操作创建者中保持所有异步行为
export function fetchData() {
return {
types: [ FETCH_DATA, FETCH_DATA_SUCCESS, FETCH_DATA_FAILURE ],
promise: api => api('foo/bar')
}
}
function getData() {
const apiUrl = '/fetch-data';
return (dispatch, getState) => {
dispatch({
type: 'DATA_FETCH_LOADING'
});
ajax({
url: apiUrl,
}).done((data) => {
dispatch({
type: 'DATA_FETCH_SUCCESS',
data: data
});
}).fail(() => {
dispatch({
type: 'DATA_FETCH_FAIL'
});
});
};
}
我的自定义中间件看到对象有一个types
数组和一个promise
函数,并专门处理它。下面是它的样子:
import 'whatwg-fetch';
function isRequest({ promise }) {
return promise && typeof promise === 'function';
}
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
const error = new Error(response.statusText || response.status);
error.response = response.json();
throw error;
}
}
function parseJSON(response) {
return response.json();
}
function makeRequest(urlBase, { promise, types, ...rest }, next) {
const [ REQUEST, SUCCESS, FAILURE ] = types;
// Dispatch your request action so UI can showing loading indicator
next({ ...rest, type: REQUEST });
const api = (url, params = {}) => {
// fetch by default doesn't include the same-origin header. Add this by default.
params.credentials = 'same-origin';
params.method = params.method || 'get';
params.headers = params.headers || {};
params.headers['Content-Type'] = 'application/json';
params.headers['Access-Control-Allow-Origin'] = '*';
return fetch(urlBase + url, params)
.then(checkStatus)
.then(parseJSON)
.then(data => {
// Dispatch your success action
next({ ...rest, payload: data, type: SUCCESS });
})
.catch(error => {
// Dispatch your failure action
next({ ...rest, error, type: FAILURE });
});
};
// Because I'm using promise as a function, I create my own simple wrapper
// around whatwg-fetch. Note in the action example above, I supply the url
// and optionally the params and feed them directly into fetch.
// The other benefit for this approach is that in my action above, I can do
// var result = action.promise(api => api('foo/bar'))
// result.then(() => { /* something happened */ })
// This allows me to be notified in my action when a result comes back.
return promise(api);
}
// When setting up my apiMiddleware, I pass a base url for the service I am
// using. Then my actions can just pass the route and I append it to the path
export default function apiMiddleware(urlBase) {
return function() {
return next => action => isRequest(action) ? makeRequest(urlBase, action, next) : next(action);
};
}
导入'whatwg fetch';
函数isRequest({promise}){
返回承诺&&typeof承诺==='function';
}
功能检查状态(响应){
如果(response.status>=200&&response.status<300){
返回响应;
}否则{
常量错误=新错误(response.statusText | | response.status);
error.response=response.json();
投掷误差;
}
}
函数parseJSON(响应){
返回response.json();
}
函数makeRequest(urlBase,{promise,types,…rest},next){
const[请求、成功、失败]=类型;
//分派您的请求操作,以便UI可以显示加载指示器
下一步({…rest,类型:REQUEST});
常量api=(url,参数={})=>{
//默认情况下获取不包含相同的原始标头。默认情况下添加此标头。
params.credentials='相同来源';
params.method=params.method | |“get”;
params.headers=params.headers | |{};
params.headers['Content-Type']='application/json';
参数头['Access-Control-Allow-Origin']='*';
返回获取(urlBase+url,参数)
.然后(检查状态)
.then(解析JSON)
。然后(数据=>{
//派遣你的成功行动
下一步({…rest,负载:数据,类型:SUCCESS});
})
.catch(错误=>{
//派遣你的失败行动
下一步({…rest,error,type:FAILURE});
});
};
//因为我使用promise作为函数,所以我创建了自己的简单包装器
//注意,在上面的操作示例中,我提供了url
//和可选参数,并将它们直接输入fetch。
//这种方法的另一个好处是,在我上面的行动中,我可以做到
//var result=action.promise(api=>api('foo/bar'))
//然后(()=>{/*发生了什么事*/})
//这允许我在返回结果时在操作中得到通知。
退货承诺(api);
}
//在设置我的API中间件时,我传递我所使用的服务的基本url
//使用。然后,我的操作就可以通过路由,并将其附加到路径
导出默认函数API中间件(urlBase){
返回函数(){
return next=>action=>isRequest(action)?makeRequest(urlBase,action,next):next(action);
};
}
我个人喜欢这种方法,因为它集中了很多逻辑,并为您提供了api操作的结构标准实施。这样做的缺点是,对于那些不熟悉redux的人来说,它可能有点神奇。我还使用thunk中间件,这两种中间件一起解决了我目前的所有需求。两种方法都不是更好的,因为它们是相同的。无论您是在回调中调度操作还是使用redux thunk,您都有效地做到了以下几点:
function asyncActionCreator() {
// do some async thing
// when async thing is done, dispatch an action.
}
就我个人而言,我更喜欢跳过中间件/thunk,只使用回调。我并不认为与中间件/thunks相关的额外开销是必要的,编写自己的“async action creator”函数也不是那么困难:
var store = require('./path-to-redux-store');
var actions = require('./path-to-redux-action-creators');
function asyncAction(options) {
$.ajax({
url: options.url,
method: options.method,
success: function(response) {
store.dispatch(options.action(response));
}
});
};
// Create an async action
asyncAction({
url: '/some-route',
method: 'GET',
action: actions.updateData
});
我认为您真正要问的是,是在action creator中调用AJAX,还是在组件中调用AJAX 如果你的应用程序足够小,那么把它放在你的组件中就可以了。但是当你的应用程序越来越大时,你会想要重构。在更大的应用程序中,您希望您的组件尽可能简单和可预测。在组件中使用AJAX调用会大大增加其复杂性。此外,在action creator中使用AJAX调用使其更易于重用 惯用的Redux方法是将所有异步调用放入动作创建者中。这使得应用程序的其余部分更容易预测。您的组件总是同步的。你的减速器总是同步的 异步操作创建者的唯一要求是
redux thunk
。使用redux-thunk
不需要了解中间件的细节,只需要知道在创建应用商店时如何应用它
采取了以下措施:
function getData() {
const apiUrl = '/fetch-data';
return (dispatch, getState) => {
dispatch({
type: 'DATA_FETCH_LOADING'
});
ajax({
url: apiUrl,
}).done((data) => {
dispatch({
type: 'DATA_FETCH_SUCCESS',
data: data
});
}).fail(() => {
dispatch({
type: 'DATA_FETCH_FAIL'
});
});
};
}