在redux-observable中 - 我如何发送一个动作" mid-stream"没有返回该动作的值?

时间:2017-12-08 12:51:27

标签: reactjs redux rxjs5 redux-observable

我到处搜索,找不到以下答案...

在我的addItemEpic中,我想发送一个" loading"发送ajax请求之前的动作 - 我不希望返回动作的值 - 我只想做动作然后返回ajax响应。 这可以通过store.dispatch()轻松实现,如下所示:

export const addItemsEpic = (action$, store) =>
  action$.ofType(ADD_ITEM)
    .do(() => store.dispatch({
        type: 'layout/UPDATE_LAYOUT',
        payload: { loading: true } 
    }))
    .switchMap(action => api.addItem(action.payload))
    .map(item => loadItem(item))
    .do(() => store.dispatch({
       type: 'layout/UPDATE_LAYOUT',
       payload: { loading: false } 
    }))
    .catch(error => Observable.of({
       type: 'AJAX_ERROR',
       payload: error,
       error: true
   }));

但是不推荐使用store.dispatch(),并且不建议使用redux-observable。

我试图使用几乎所有合理的操作符,但仍然无法在不中断下一个链接函数中的返回值的情况下调度操作。我认为像te一样的事情应该这样做:

action$.ofType(ADD_ITEM)
   .concatMap(action => Observable.of(
      updateLayout({loading: true}),
      api.addItem(action.payload)).last())
   .map(item => loadItem(item))

但不幸的是,这里有两个问题:

  • 没有调度load-action(值IS作为observable返回)
  • api请求现在返回一个包装的promise而不是mappable值

我非常感谢这里的一些帮助或建议,因为我无法找到任何方法来实现这一目标。

提前致谢

2 个答案:

答案 0 :(得分:1)

第二种形式是在正确的道路上。我们的想法是返回首先发出加载操作的Observable,然后在服务调用之后发出loaded操作或error操作,最后发出完成加载操作。< / p>

const addItemEpic = action$ => action$
  .ofType(ADD_ITEM)
  .switchMap(action => { // or use mergeMap/concatMap depending on your needs
    const apiCall$ = Observable.fromPromise(api.addItem(action.payload))
      .map(item => actions.loadItem(item))
      .catch(err => Observable.of(actions.ajaxError(err)));

    return Observable.concat(
      Observable.of(actions.updateLayout(true)),
      Observable.concat(apiCall$,
        Observable.of(actions.updateLayout(false))));
  });

Observable.fromPromise()Promise转换为Observable

答案 1 :(得分:1)

罗伯特的答案很棒,也是正确答案。以下是我的一些额外想法:

重复使用现有操作

很多时候,当你发现自己同步跟随一个又一个动作时,这表明你的减速器只需要第一个。

e.g。除了layout/UPDATE_LAYOUT设置loading: true之外,您的减速器可以知道只要有ADD_ITEMLOAD_ITEM,就意味着它应该是loading: true。如果隐含性不是你的包,那么你可以使用额外的元数据作为信号而不是动作类型本身。

const layoutReducer = (state = { loading: false }, action) => {
  // instead of listening for an action type, we're looking for some other 
  // metadata by convention. In this case the `layout` property.
  if (action.layout) {
    return { ...state, ...action.layout };
  } else {
    return state;
  }
};

// the schema you use (payload vs layout vs metadata, etc) is your call
store.dispatch({
  type: 'DOESNT_MATTER',
  payload: 'whatever',
  layout: { loading: true }
});

所有这一切,你的例子可能已被简化,以便更容易询问SO(一个好主意)。有时,为了清晰,分离关注点,重新分配,或者仅仅因为你拥有而将它们分开,因为你不控制正在监听它的库,确实有意义。例如react-router-redux您需要发送特殊操作来进行路由转换。

所以想到这就像“记住”建议而不是教义:)

的concat

concat支持任意数量的参数,因此您只需要其中一个参数:

const addItemEpic = action$ => action$
  .ofType(ADD_ITEM)
  .switchMap(action => {
    const apiCall$ = Observable.fromPromise(api.addItem(action.payload))
      .map(item => actions.loadItem(item))
      .catch(err => Observable.of(actions.ajaxError(err)));

    return Observable.concat(
      Observable.of(actions.updateLayout(true)),
      apiCall$,
      Observable.of(actions.updateLayout(false))
    );
  });

// or 

const addItemEpic = action$ => action$
  .ofType(ADD_ITEM)
  .switchMap(action =>
    Observable.concat(
      Observable.of(actions.updateLayout(true))
      Observable.fromPromise(api.addItem(action.payload))
        .map(item => actions.loadItem(item))
        .catch(err => Observable.of(actions.ajaxError(err))),
      Observable.of(actions.updateLayout(false))
    )
  );