在 redux-observable 中 - 如何在不返回该操作的值的情况下分派操作 "mid-stream"?

In redux-observable - How can I dispatch an action "mid-stream" without returning the value of that action?

我到处搜索都找不到以下问题的答案...

在我的 addItemEpic 中,我想在发送 ajax 请求之前分派一个 "loading" 操作 - 我不希望操作的值被 returned - 我只是想要执行操作然后 return 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 中被弃用和劝阻。

我已经尝试使用几乎所有可能的运算符,但我仍然无法在不破坏下一个链接函数中的 return 值的情况下发送操作。我认为像下面这样的东西应该可以做到:

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

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

我真的很感谢这里的一些帮助或建议,因为我找不到任何方法来让它工作。

提前致谢

第二种形式走在正确的道路上。这个想法是 return 一个 Observable 首先发出加载动作,然后在服务调用之后发出 loaded 动作或 error 动作,最后发出完成加载动作。

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

罗伯特的回答很好而且很正确。以下是我的一些额外想法:

重用现有操作

很多时候,当您发现自己同步执行一个又一个操作时,这表明您的减速器应该只需要第一个。

例如而不是让 layout/UPDATE_LAYOUT 设置 loading: true,你的 reducer 可以知道只要有 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 您需要调度特殊操作来进行路由转换。

所以将此视为 "keep it in mind" 建议而不是教条 :)

连接

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))
    )
  );