redux-observable dispatch actions

2019-08-26 08:50发布

问题:

I need to dispatch some actions in some order using redux-observable however, it takes just last action to dispatch. Please see example:

export const fetchClientsEpic = (action$, { dispatch }) =>
  action$
    .ofType(fetchClients)
    .mapTo(fetchClientsPending(true))
    .mergeMap(() => {
      return ajax
        .getJSON('some/get/clients/api')
        .map((clients: IClient[]) => {
          return fetchClientsSuccess(
            map(clients, (client, index) => ({
              key: index,
              ...client,
            })),
          );
        });
    });

fetchClientsSuccess is dispatched with clients but fetchClientsPending not, I totally do not get it why. I could use dispatch because I get it in params, but I feel it is not good solution(?). It should be done in the stream I guess. I am starting with RxJs and redux-observable. Is it possible to do?

回答1:

Operators are chains of Observables where the input of one stream is the output of another. So when you use mapTo you're mapping one action to the other. But then your mergeMap maps that Pending action and maps it to that other inner Observable that does the ajax and such, effectively throwing the Pending action away. So think of RxJS as a series of pipes where data flows through (a stream)

While there is no silver bullet, in this particular case what you want to achieve can be done by using startWith at the end of your inner Observable

export const fetchClientsEpic = (action$, { dispatch }) =>
  action$
    .ofType(fetchClients)
    .mergeMap(() => {
      return ajax
        .getJSON('some/get/clients/api')
        .map((clients: IClient[]) => {
          return fetchClientsSuccess(
            map(clients, (client, index) => ({
              key: index,
              ...client,
            })),
          );
        })
        .startWith(fetchClientsPending(true)); // <------- like so
    });

This is in fact the same thing as using concat with of(action) first, just shorthand.

export const fetchClientsEpic = (action$, { dispatch }) =>
  action$
    .ofType(fetchClients)
    .mergeMap(() => {
      return Observable.concat(
        Observable.of(fetchClientsPending(true)),
        ajax
          .getJSON('some/get/clients/api')
          .map((clients: IClient[]) => {
            return fetchClientsSuccess(
              map(clients, (client, index) => ({
                key: index,
                ...client,
              })),
            );
          })
      );
    });

That said, I would recommend against synchronously dispatching another action to set the state that fetching is pending and instead rely on the original fetchClients action itself for the same effect. It should be assumed by your reducers that if such an action is seen, that some how the fetching still start regardless. This saves you the boilerplate and helps a bit on micro-perf since you don't need to run through the reducers, epics, and rerender twice.

There's no rules though, so if you feel strongly about this, go for it :)