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

Dispatch async redux action from non-react component with thunk middleware

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

现在的问题是我的 api-service 不是 React 组件,所以我无法获得对 dispatchactions 的引用。 我首先做的是导出商店并手动调用 dispatch,但正如我在这里读到的那样 这似乎是一种不好的做法,因为它要求商店是单例的,这使得测试和渲染变得困难在服务器上是不可能的,因为我们需要为每个用户提供不同的商店。

我已经在使用 react-thunk (https://github.com/gaearon/redux-thunk) 但我没有t see how I can injectdispatch` 到非反应组件中。

我需要做什么?或者从 React 组件外部调度动作通常是一种不好的做法? 这就是我的 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;
};

谢谢!

如果您使用的是 redux-thunk,您可以 return 来自 action creator 的函数,它有 dispatch 有参数:

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

另一种选择是对远程内容使用中间件。这是可行的,中间可以测试动作的类型,然后将其转换为一个或多个其他动作。看看here,也差不多,虽然基本都是动画,答案最后还是解释一下如何使用中间件进行远程请求

你应该让你的 api 调用完全独立于 redux。它应该 return 一个承诺(就像它目前所做的那样),在满意的情况下解决并用一个告诉状态的参数拒绝。像

if (statusAsString === '401') {
  reject({ logout: true })
}
reject({ logout: false });

然后在你的 action creator 代码中你会做:

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(...)
  }
}

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

api: 抛出错误

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

action creator:捕获来自 api 的错误,并调度错误 action

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

您还可以使用 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())
}