简体   繁体   English

如何以正确的顺序从Redux中间件分派操作

[英]How to dispatch actions from Redux middleware in correct order

As must of the applications in the world, my React application needs to perform some Ajax calls to an API. 与世界上所有应用程序一样,我的React应用程序需要执行对API的一些Ajax调用。

I've chosen to use Redux middlewares in order to correctly separate API fetching from my components. 我选择使用Redux中间件,以便正确地将API提取与组件分离。
The idea is to dispatch REQUEST actions from my components. 这个想法是从我的组件中分派REQUEST操作。 Middlewares listen for them and dispatch SUCCESS or ERROR actions: these last are listened by the reducers. 中间件侦听它们并调度SUCCESSERROR操作:这些最后的内容由reducer侦听。

A lot of people here have already asked how to dispatch actions from Redux middlewares: this is NOT the topic of my question here :) 这里很多人已经问过如何从Redux中间件分派操作:这不是我这里的问题的主题:)

First, let me show you a simple reducer I use to write: 首先,让我向您展示一个我用来编写的简单的reducer:

function currentUserReduxer(state = {}, action = {}) {
  const { currentUser, error } = action.payload;

  switch (action.type) {
    case 'GET_CURRENT_USER_REQUEST':
      return { ...state, isFetching: true, error: null };

    case 'GET_CURRENT_USER_SUCCESS':
      return { ...state, id: currentUser.id, isFetching: false };

    case 'GET_CURRENT_USER_FAILURE':
      return { ...state, isFetching: false, error };

    default:
      return state;
  }
}

And the corresponding middleware: 以及相应的中间件:

() => next => async (action) => {
  next(action);

  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();
        next(actions.getCurrentUserSuccess(currentUser));
      } catch (error) {
        next(actions.getCurrentUserFailure(error));
      }
      break;
    }

    default:
      break;
  }
};

It worked well since a long time, and then I realized it's partially wrong: my middleware does not return the value of next , so it breaks the middleware chain and it's wrong! 长期以来它一直运行良好,然后我意识到部分错误:我的中间件不返回next的值,因此它中断了中间件链,这是错误的!
Since next(action); next(action); was the first thing I've performed in the middleware, I couldn't return it this soon, so I've moved it at the end of the middleware. 这是我在中间件中执行的第一件事,我不能马上返回它,因此我将其移至中间件末尾。 I've also decided to dispatch new actions instead of using next for them (after all, they ARE new actions, it does make sense to send them to the whole chain of middlewares). 我还决定调度新操作,而不是对它们使用next (毕竟,它们是新操作,将它们发送到整个中间件链中确实很有意义)。 My new middleware look like this now: 我的新中间件现在看起来像这样:

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();
        store.dispatch(actions.getCurrentUserSuccess(currentUser));
      } catch (error) {
        store.dispatch(actions.getCurrentUserFailure(error));
      }
      break;
    }

    default:
      break;
  }

  return next(action);
};

It looks great, but I have now another issue: since store.dispatch is synchronous, next(action) is called after. 看起来不错,但我现在遇到另一个问题:由于store.dispatch是同步的,因此将在调用next(action)之后。 That means that my reducers receive the REQUEST action AFTER the SUCCESS or FAILURE ones :( 这意味着我的减速器在SUCCESSFAILURE之后会收到REQUEST动作 :(

I think one solution could be using good old promises instead of await : 我认为一种解决方案可能是使用良好的旧承诺而不是await

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      api.getCurrentUser()
        .then((currentUser) => {
          store.dispatch(actions.getCurrentUserSuccess(currentUser));
        })
        .catch((error) => {
          store.dispatch(actions.getCurrentUserFailure(error));
        });
      break;
    }

    default:
      break;
  }

  return next(action);
};

Another idea would be to wrap the store.dispatch with a setTimeout : 另一个想法是用setTimeout包装store.dispatch

store => next => async (action) => {
  switch (action.type) {
    case'GET_CURRENT_USER_REQUEST': {
      try {
        const currentUser = await api.getCurrentUser();

        setTimeout(() => store.dispatch(actions.getCurrentUserSuccess(currentUser)));
      } catch (error) {
        setTimeout(() => store.dispatch(actions.getCurrentUserFailure(error)));
      }
      break;
    }

    default:
      break;
  }

  return next(action);
};

To be honest, I don't really like these two solutions, they feel so hacky... 老实说,我不太喜欢这两种解决方案,它们感觉很笨拙...

So here is my question: how am I suppose to handle my problem? 所以这是我的问题:我应该如何处理我的问题? Is there a more clean way to do that? 有没有更干净的方法可以做到这一点?

Thanks in advance :) 提前致谢 :)

Seems like what you are trying to do is similar to Redux-Saga I suggest you take a look at their library. 似乎您要尝试的操作与Redux-Saga类似,建议您看一下它们的库。

Taken from their example 取自他们的例子

// worker Saga: will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

/*
  Starts fetchUser on each dispatched `USER_FETCH_REQUESTED` action.
  Allows concurrent fetches of user.
*/
function* mySaga() {
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

I think your first solution is near to the clean answer. 我认为您的第一个解决方案已经接近答案。 You can introduce the term sideEffects which are async functions executed whenever your action is dispatch. 您可以引入术语sideEffects ,它们是在分派操作时执行的异步函数。 Your middleware function is still synchronize and the action will be dispatch right away. 您的中间件功能仍在同步,操作将立即分派。 Here is an example: 这是一个例子:

getCurrentUserSideEffects = async (action) => {
    api.getCurrentUser()
        .then((currentUser) => {
          store.dispatch(actions.getCurrentUserSuccess(currentUser));
        })
        .catch((error) => {
          store.dispatch(actions.getCurrentUserFailure(error));
        });
}

<other side effects>

store => next => action => {
  switch (action.type) {
    case 'GET_CURRENT_USER_REQUEST': {
      getCurrentUserSideEffects(action);
      break;
    }

    default:
      break;
  }

  return next(action);
};

I think this idea is pretty similar to redux saga, but simpler and easier to understand. 我认为这个想法与redux saga非常相似,但是更容易理解。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM