使用thunk中间件从非反应组件调度异步redux操作

时间:2017-04-12 12:03:27

标签: reactjs asynchronous redux react-redux redux-thunk

我正在构建一个react / redux webapp,我正在使用一个服务来进行所有的API调用。每当API返回401 - Unauthorized时,我都想将注销操作发送到我的redux商店。

现在问题是我的api-service没有反应组件,因此我无法获得对dispatchactions的引用。 我首先做的是导出商店并手动调用dispatch,但正如我在这里阅读How to dispatch a Redux action with a timeout?这似乎是一个不好的做法,因为它要求商店是一个单身,这使得测试很难和渲染在服务器上不可能,因为我们需要为每个用户提供不同的商店。

我已经在使用react-thunkhttps://github.com/gaearon/redux-thunk),但我不会t see how I can inject发送给非反应组件。

我需要做什么?或者从反应组件外部调度操作通常是一种不好的做法? 这就是我api.services.ts现在的样子:

... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';

export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
  let promise = new Promise((resolve, reject) => {
    const headers = {
      "Content-Type": "application/json",
      "Authorization": getFromStorage('auth_token')
    };
    const options = {
      body: data ? JSON.stringify(data) : null,
      method,
      headers
    };
    fetch(url, options).then((response) => {
      const statusAsString = response.status.toString();
      if (statusAsString.substr(0, 1) !== '2') {
        if (statusAsString === '401') {
          //  !!!!!-> here I need to dispatch the logout action
          store.dispatch(UserActions.logout());
        }
        reject();
      } else {
        saveToStorage('auth_token', response.headers.get('X-TOKEN'));
        resolve({
          data: response.body,
          headers: response.headers
        });
      }
    })
  });
  return promise;
};

谢谢!

4 个答案:

答案 0 :(得分:1)

如果您正在使用redux-thunk,则可以从动作创建者返回一个函数,其中dispatch具有参数:

const doSomeStuff = dispatch => {
  fetch(…)
   .then(res => res.json())
   .then(json => dispatch({
     type: 'dostuffsuccess',
     payload: { json }
    }))
    .catch(err => dispatch({
      type: 'dostufferr',
      payload: { err }
     }))
}

另一种选择是将中间件用于远程内容。这样做的方式,中间可以测试一个动作的类型,然后将其转换为on或多个其他动作。看看here,它是相似的,即使基本上是关于动画,答案最后还有关于如何使用中间件进行远程请求的一些解释。

答案 1 :(得分:0)

你应该让你的api调用完全独立于redux。它应该返回一个promise(就像它当前那样),在happy case中解析并拒绝一个告诉状态的参数。像

这样的东西
if (statusAsString === '401') {
  reject({ logout: true })
}
reject({ logout: false });

然后在您的动作创建者代码中执行:

function fetchWithAuthAction(url, method, data) {

  return function (dispatch) {
    return fetchWithAuth(url, method, data).then(
      ({ data, headers }) => dispatch(fetchedData(data, headers)),
      ({ logout }) => {
        if(logout) {
          dispatch(UserActions.logout());
        } else {
          dispatch(fetchedDataFailed());
        }
    );
  };
}

修改

如果您不想在任何地方编写错误处理代码,可以创建帮助程序:

function logoutOnError(promise, dispatch) {
  return promise.catch(({ logout }) => {
    if(logout) {
      dispatch(UserActions.logout());
    }
  })
}

然后你可以在你的动作创作者中使用它:

function fetchUsers() {
  return function (dispatch) {
    return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
  }
}

答案 2 :(得分:0)

也许您可以尝试使用中间件来捕获错误并发送注销操作, 但在这种情况下,问题是你必须在动作创建者中发送需要检查日志状态的错误

api:抛出错误

        if (statusAsString === '401') {
          //  !!!!!-> here I need to dispatch the logout action
          throw new Error('401')
        }

动作创建者:从api捕获错误,并发送错误操作

    fetchSometing(ur)
      .then(...)
      .catch(err => dispatch({
        type: fetchSometingError,
        err: err 
       })

中间件:使用401消息捕获错误,并发送注销操作

const authMiddleware = (store) => (next) => (action) => {
  if (action.error.message === '401') {
    store.dispatch(UserActions.logout())
  }
}

答案 3 :(得分:0)

您还可以使用axios(拦截器)或apisauce(监视器)并在他们前往其处理程序之前拦截所有呼叫,并在此时使用

// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status

if (response.status === '401') {
    store.dispatch(UserActions.logout())
}