LoginSignup
2
0

More than 3 years have passed since last update.

freeletics/RxReduxのReducerとSideEffeftの呼び出し順序を調べた

Posted at

feeletics/RxReduxとは

RxJavaを利用して、Reduxの流れをRxのストリームに乗せて実行するフレームワーク。
AndroidでReduxアーキテクチャを採用するときに使えるはず!と調査に乗り出した。

Android + Reduxについてはこちらを参照

想定読者

  • RxJavaはまあまあわかる
  • Kotlinがちょっと読める
  • feeletics/RxReduxを使ってみようと思ったが、ReducerとSideEffectの呼び出し順序がわからない人(タイトル通り)

検証内容

githubに書いてある内容と見た目が違う挙動をしているように見えたので調査してみた。
結論としては「readmeは正しいが、ちょっと誤解しやすい気がする」

検証コード

action.reduxStore(
    initState(), // 今回は関係ないので無視してください
    listOf(this.sideEffect1, this.sideEffect2), // SideEffectを設定
    this.reducer,  // reducerを設定します。
).subscribe{}

このsideEffectの挙動によって、どのようにstreamの流れが変わるのか調査します。

sideEffect1もsideEffect2も後ろにactionを流さない場合

private val sideEffect1: SideEffect<State, Action> = { a, _ ->
    a.doOnNext { Timber.d("test1 log") }.flatMap { Observable.empty<Action>() }
}
// sideEffect2も同様

結果
reducer -> sideEffect1 -> sideEffect2
考察

reducer -> sideEffect1
        -> sideEffect2

sideEffect2が後ろにactionを流す場合

private val sideEffect1: SideEffect<State, Action> = { a, _ ->
    a.doOnNext { Timber.d("test1 log") }.flatMap { Observable.empty<Action>() }
}
private val sideEffect2: SideEffect<State, Action> = { a, _ ->
    a.doOnNext { Timber.d("test1 log") }
}

結果
reducer -> sideEffect1 -> sideEffect2 -> reducer -> sideEffect1 -> sideEffect2 -> reducer -> 以下ループ
考察

reducer -> sideEffect1
        -> sideEffect2 -> reducer -> sideEffect1
                                  -> sideEffect2 -> reducer -> 以下ループ

sideEffect1が後ろにactionを流す場合

private val sideEffect1: SideEffect<State, Action> = { a, _ ->
    a.doOnNext { Timber.d("test1 log") }
}
private val sideEffect2: SideEffect<State, Action> = { a, _ ->
    a.doOnNext { Timber.d("test1 log") }.flatMap { Observable.empty<Action>() }
}

結果
reducer -> sideEffect1 -> reducer -> sideEffect1 -> 以下ループ
考察

reducer -> sideEffect1 -> reducer -> sideEffect1 -> 以下ループ
        -> (sideEffect2)          
                                  -> (sideEffect2)
* sideEffect2も呼ばれているが、sideEffect1のループがスタックしており処理が実行されていない

sideEffect1が一度だけactionを流す場合

// 1回目にはFirstAction()が流れてくる
private val sideEffect1: SideEffect<State, Action> = { a, _ ->
    a.doOnNext { Timber.d("test1 log") }.filter { it is FirstAction() }.map { SecondAction() }
}
private val sideEffect2: SideEffect<State, Action> = { a, _ ->
    a.doOnNext { Timber.d("test1 log") }.flatMap { Observable.empty<Action>() }
}

結果
reducer -> sideEffect1 -> reducer -> sideEffect1 -> sideEffect2 -> sideEffect2
考察

reducer -> sideEffect1 -> reducer -> sideEffect1
        -> sideEffect2
                                  -> sideEffect2

結論

action -> reducer -> sideEffect1 -> reducer -> sideEffect1
                  -> sideEffect2            -> sideEffect2
                        ・                          ・
                        ・                          ・
                     sideEffectn ->         -> sideEffectn

といった具合に、reducerの後に各種sideEffectが呼ばれ、
sideEffectが後ろにactionを流すと、またreducerが呼ばれその後に各種sideEffectが呼ばれる。
よって、各種sideEffectは自分が見たいactionのみ見るように以下のように実装することが望ましい。

private val sideEffect1: SideEffect<State, Action> = { a, _ ->
    a.ofType(FirstAction::class.java).doOnNext { Timber.d("test1 log") }
}
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0