5

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

我的应用程序从 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 的文章时,我已经确定了两种可能的方法,可用于处理在 store 中存储我的成功数据:

  • 使用异步中间件,或
  • ADD_DATA从 ajax 函数的成功回调中调度动作

但我不确定哪种方法更好。

在回调中调度动作听起来很容易实现和理解,而异步中间件更难向不习惯使用函数式语言的人解释。

4

4 回答 4

11

我个人更喜欢使用自定义中间件来实现这一点。它使操作更容易遵循,并且 IMO 样板更少。

我已经设置了我的中间件来查找从与某个签名匹配的操作返回的对象。如果找到这个对象模式,它会专门处理它。

例如,我使用如下所示的操作:

export function fetchData() {
  return {
    types: [ FETCH_DATA, FETCH_DATA_SUCCESS, FETCH_DATA_FAILURE ],
    promise: api => api('foo/bar')
  }
}

我的自定义中间件看到该对象有一个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);
  };
}

我个人喜欢这种方法,因为它集中了很多逻辑,并为您提供了 api 操作结构的标准实施方式。这样做的缺点是,对于那些不熟悉 redux 的人来说,它可能有点神奇。我也使用 thunk 中间件,到目前为止,这两个中间件一起解决了我的所有需求。

于 2015-12-16T17:17:53.607 回答
3

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

对于初学者来说,在回调中调度动作似乎更容易理解,但使用中间件具有以下优点:

  • thunk 允许调度多个动作(如上面的示例 - 首先将状态设置为“请求”,可用于加载指标等)
  • 它允许有条件地调度其他操作。例如,仅当自上次获取以来的时间超过阈值时才获取
  • 您仍然可以在没有中间件的情况下实现所有这些,但使用中间件可以帮助您将所有异步行为保持在动作创建者中
于 2015-12-16T05:39:26.977 回答
2

这两种方法都不是更好,因为它们是相同的。无论您在回调中调度操作还是使用 redux thunk,您都在有效地执行以下操作:

function asyncActionCreator() {
  // do some async thing
  // when async thing is done, dispatch an action.
}

就我个人而言,我更喜欢跳过中间件/thunk,只使用回调。我真的不认为与中间件/ thunk 相关的额外开销是必要的,编写自己的“异步操作创建者”函数并不难:

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
}); 
于 2015-12-16T20:14:48.077 回答
1

我认为您真正要问的是是否在您的动作创建者或组件中进行 AJAX 调用。

如果您的应用程序足够小,则可以将其包含在您的组件中。但是随着您的应用程序变得越来越大,您将需要重构。在更大的应用程序中,您希望您的组件尽可能简单和可预测。在组件中进行 AJAX 调用会大大增加其复杂性。此外,在动作创建者中使用 AJAX 调用使其更易于重用。

惯用的 Redux 方式是将所有异步调用放在动作创建者中。这使您的应用程序的其余部分更具可预测性。您的组件始终是同步的。你的减速器总是同步的。

异步操作创建者的唯一要求是redux-thunk. 您不需要知道要使用的中间件的来龙去脉redux-thunk,您只需要知道在创建商店时如何应用它即可。

以下内容直接取自redux-thunkgithub页面:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

// create a store that has redux-thunk middleware enabled
const createStoreWithMiddleware = applyMiddleware(
    thunk
)(createStore);

const store = createStoreWithMiddleware(rootReducer);

而已。现在您可以拥有异步动作创建者。

你的看起来像这样:

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

   };

}

而已。每当动作创建者返回一个函数时,thunk 中间件就会公开dispatchgetState您可能不需要)以允许异步动作。

于 2015-12-16T22:32:45.490 回答