Asynchronous Redux:使用异步中间件与在成功函数上调度操作

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, });

我正在尝试将Redux集成到我的React项目中。 目前我没有使用任何通量框架

我的应用程序从API获取一些数据,并以漂亮的方式显示,如下所示:

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'
            });
        });

   };

}