【问题标题】:Dispatch async redux action from non-react component with thunk middleware使用 thunk 中间件从非反应组件调度异步 redux 操作
【发布时间】:2017-09-08 04:52:34
【问题描述】:

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

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

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

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

谢谢!

【问题讨论】:

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


    【解决方案1】:

    如果你使用 redux-thunk,你可以从 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,差不多吧,虽然基本都是动画,最后还是解释一下如何使用中间件进行远程请求。

    【讨论】:

    • 实际上我已经在我的UserActions 中这样做了,但是我从那个动作中调用了一个分离的api.service,我也可以将它用于与redux-action 无关的请求。但我现在所做的是始终将dispatch 从操作传递给服务。谢谢!
    【解决方案2】:

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

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

    【讨论】:

    • 感谢您的回复,但在您的示例中,我仍然需要导入商店并致电store.dispatch(UserActions.logout());(这正是我现在正在做的事情)。如我提供的链接中所述,这很糟糕,因为商店需要是单例的。还是我错过了什么?
    • 啊,我明白你的意思了。其实我不需要store.dispatch,只需dispatch。但这意味着我必须在每个 api 操作中处理拒绝,并且在我的 api.services.ts 中处理这一切会更干净
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 2021-01-22
        • 2020-01-17
        • 2016-12-20
        • 2018-09-07
        • 2021-09-14
        • 2023-03-09
        • 2018-01-31
        • 2019-11-12
        • 1970-01-01
        相关资源
        最近更新 更多