如何从redux-observable调度多个动作?

Jie*_*eng 5 javascript rxjs redux redux-observable

我想从可观看Redux的史诗中调度多个动作。我该怎么做?我最初是从

const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      return db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
        .then(() => {
          return { type: ADD_CATEGORY_SUCCESS }
        })
        .catch(err => {
          return { type: ADD_CATEGORY_ERROR, payload: err }
        })
    })
}
Run Code Online (Sandbox Code Playgroud)

现在ADD_CATEGORY_SUCCESS,我不仅要调度,还希望刷新清单(GET_CATEGORIES_REQUEST)。我尝试了很多事情,但总是得到

动作必须是普通对象。使用自定义中间件进行异步操作

例如:

const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      return db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
        .then(() => {
          return Observable.concat([
            { type: ADD_CATEGORY_SUCCESS },
            { type: GET_CATEGORIES_REQUEST }
          ])
        })
        .catch(err => {
          return { type: ADD_CATEGORY_ERROR, payload: err }
        })
    })
}
Run Code Online (Sandbox Code Playgroud)

或改变switchMapmergeMap

jay*_*lps 7

问题是,在您的内部,您switchMap将返回一个Promise,该Promise本身会解析为concat Observable;Promise<ConcatObservable>。所述switchMap操作者将听无极然后发射ConcatObservable原样,这将随后被提供给store.dispatch由终极版,可观察到的发动机罩下。rootEpic(action$, store).subscribe(store.dispatch)。由于分派Observable没有意义,这就是为什么您会收到错误消息,即操作必须是普通对象。

史诗发出的内容必须始终是简单的旧JavaScript操作对象,即{ type: string }(除非您有其他中间件来处理其他事情)

由于Promises只会发出一个值,因此我们不能使用它们来发出两个动作,因此我们需要使用Observables。因此,首先让我们将Promise转换为可以使用的Observable:

const response = db.collection('categories')
  .doc()
  .set({
    name: action.payload.name,
    uid: user.uid
  })

// wrap the Promise as an Observable
const result = Observable.from(response)
Run Code Online (Sandbox Code Playgroud)

现在,我们需要将该Observable映射为多个操作,该Observable将发出一个值。该map运营商没有做一个一对多的,而不是我们将要使用的一个mergeMapswitchMapconcatMap,或exhaustMap。在这种非常特殊的情况下,我们选择哪种情况都没有关系,因为我们将其应用于(封装了Promise的)Observable只会发出一个值,然后再完成()。也就是说,了解这些运算符之间的区别非常重要,因此一定要花一些时间来研究它们。

我将使用mergeMap(再次,在此特定情况下这无关紧要)。因为mergeMap期望我们返回一个“流”(一个Observable,Promise,迭代器或数组),所以我将使用它Observable.of来创建我们要发出的两个动作的Observable。

Observable.from(response)
  .mergeMap(() => Observable.of(
    { type: ADD_CATEGORY_SUCCESS },
    { type: GET_CATEGORIES_REQUEST }
  ))
Run Code Online (Sandbox Code Playgroud)

这两个动作将按照我提供的顺序依次同步发送。

我们也需要添加错误处理,因此我们将使用catchRxJS中的运算符-它与catchPromise上的方法之间的区别很重要,但不在此问题范围之内。

Observable.from(response)
  .mergeMap(() => Observable.of(
    { type: ADD_CATEGORY_SUCCESS },
    { type: GET_CATEGORIES_REQUEST }
  ))
  .catch(err => Observable.of(
    { type: ADD_CATEGORY_ERROR, payload: err }
  ))
Run Code Online (Sandbox Code Playgroud)

放在一起,我们将得到这样的东西:

const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      const response = db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })

      return Observable.from(response)
        .mergeMap(() => Observable.of(
          { type: ADD_CATEGORY_SUCCESS },
          { type: GET_CATEGORIES_REQUEST }
        ))
        .catch(err => Observable.of(
          { type: ADD_CATEGORY_ERROR, payload: err }
        ))
    })
}
Run Code Online (Sandbox Code Playgroud)

当此方法有效并回答您的问题时,多个reducer可以从同一单个动作进行状态更改,而这通常是人们应该做的。顺序发出两个动作通常是一种反模式。

话虽如此,这在编程中并不常见。确实有很多时候采取单独的行动更有意义,但这是例外。您更容易知道这是否是例外情况之一。只要记住它。


Ole*_*ruk 3

为什么使用 Observable.concat?SwitchMap 等待 Observable 或 Promise,其中包含来自其回调函数的值(在我们的例子中为操作数组)。因此不需要在返回的 Promise 成功处理程序中返回 Observable。试试这个:

const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      return db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
        .then(() => {
          return ([
            { type: ADD_CATEGORY_SUCCESS },
            { type: GET_CATEGORIES_REQUEST }
          ])
        })
        .catch(err => {
          return { type: ADD_CATEGORY_ERROR, payload: err }
        })
    })
}
Run Code Online (Sandbox Code Playgroud)