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