web-dev-qa-db-ja.com

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

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

または、switchMapmergeMapなどに変更します

7
Jiew Meng

問題は、switchMap内で、それ自体がconcatObservableに解決されるPromiseを返すことです。 _Promise<ConcatObservable>_。 switchMapオペレーターは、Promiseをリッスンし、ConcatObservableをそのまま発行します。これは、redux-observableによって内部で_store.dispatch_に提供されます。 rootEpic(action$, store).subscribe(store.dispatch)。 Observableをディスパッチしても意味がないため、アクションはプレーンオブジェクトである必要があるというエラーが発生します。

エピックが出力するものは、常にプレーンな古いJavaScriptアクションオブジェクトである必要があります。つまり、_{ type: string }_(他のものを処理するための追加のミドルウェアがない場合)

Promisesは単一の値しか発行しないため、2つのアクションを発行するためにそれらを使用することはできません。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)
_

次に、単一の値を発行するObservableを複数のアクションにマップする必要があります。 map演算子は、1対多ではありません。代わりに、mergeMapswitchMapconcatMap、またはexhaustMapのいずれかを使用する必要があります。この非常に特殊なケースでは、適用しているObservable(Promiseをラップする)は単一の値のみを発行してからcomplete()を実行するため、どちらを選択してもかまいません。とはいえ、これらの演算子の違いを理解することは重要なので、必ず時間をかけて調査してください。

mergeMapを使用します(ここでも、この場合は関係ありません特定のの場合)。 mergeMapは「ストリーム」(Observable、Promise、iterator、またはarray)を返すことを期待しているので、_Observable.of_を使用して、発行する2つのアクションのObservableを作成します。

_Observable.from(response)
  .mergeMap(() => Observable.of(
    { type: ADD_CATEGORY_SUCCESS },
    { type: GET_CATEGORIES_REQUEST }
  ))
_

これらの2つのアクションは、私が提供した順序で同期的かつ順次に発行されます。

エラー処理も追加する必要があるため、RxJSのcatch演算子を使用します。これとPromiseのcatchメソッドとの違いは重要ですが、この質問の範囲外です。

_Observable.from(response)
  .mergeMap(() => Observable.of(
    { type: ADD_CATEGORY_SUCCESS },
    { type: GET_CATEGORIES_REQUEST }
  ))
  .catch(err => Observable.of(
    { type: ADD_CATEGORY_ERROR, payload: err }
  ))
_

すべてをまとめると、次のようなものになります。

_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 }
        ))
    })
}
_

これは機能し、質問に答えますが、複数のレデューサーが同じ単一のアクションから状態を変更できます。これは、ほとんどの場合、代わりに行う必要があります。 2つのアクションを順番に実行することは、通常、アンチパターンです。

とはいえ、プログラミングで一般的であるように、これは絶対的なルールではありません。別々のアクションを実行する方が理にかなっている場合もありますが、それは例外です。これがそれらの例外的なケースの1つであるかどうかを知るためのより良い立場にあります。覚えておいてください。

7
jayphelps

なぜ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 }
        })
    })
}
2

エピック内のストアを使用することもできます

const addCategoryEpic = (action$, store) => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      const query = db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
       return Observable.fromPromise(query)
    }).map((result) => {
      store.dispatch({ type: ADD_CATEGORY_SUCCESS });
      return ({ type: GET_CATEGORIES_REQUEST })
   })
  .catch(err => {
     return { type: ADD_CATEGORY_ERROR, payload: err }
   })
}
1
Denis Rybalka

Observable.fromPromiseを使用してオブザーバブルを作成してから、flatMapを使用して複数のアクションを実行する必要があることがわかりました

const addCategoryEpic = action$ => {
  return action$.ofType(ADD_CATEGORY_REQUEST)
    .switchMap((action) => {
      const db = firebase.firestore()
      const user = firebase.auth().currentUser
      const query = db.collection('categories')
        .doc()
        .set({
          name: action.payload.name,
          uid: user.uid
        })
      return Observable.fromPromise(query)
        .flatMap(() => ([
          { type: ADD_CATEGORY_SUCCESS },
          { type: GET_CATEGORIES_REQUEST }
        ]))
        .catch(err => {
          return { type: ADD_CATEGORY_ERROR, payload: err }
        })
    })
}
1
Jiew Meng

RxJs6の場合:

action$.pipe(
  concatMap(a => of(Action1, Action2, Action3))
)

仕事をすることができるconcatMapmergeMap、およびswitchMapがあることに注意してください。違いは次のとおりです。 https://www.learnrxjs.io/operators/transformation /mergemap.html

0
keos