Reduxへの正しい解釈の話

Kohei
4 min readJan 26, 2017

--

2016年の課題は状態遷移の管理だったと思う。

そのアンサーとして、

  • Fluxのような実装におけるStore相当にアプリケーションの状態をほぼすべて管理させる
  • ReactのようなVirtual DOMを搭載したビューの実装を透過的なユーザーインターフェースとして扱う

この2つの組み合わせにより、アプリケーションの状態と描画される画面が (ほぼ) 参照透過的になる。というのがFluxとReact以降のパラダイムだと思う (理論として) 。

このパラダイムなら、エラーの発生時にアプリケーションの状態を表現するJSONをエラー収集サービスに送るようにして、簡単にバグを再現したりできるし、状態の遷移をテストしていくことで、クラッシュするようなバグのうち大半を検出できる。

Fluxの問題

そこで問題が出るのが、Action(Creator) とReducer (Store#reduce())の2要素間のループだけでは、現状ありうるケースに対応できないことがあることだ。

例えば、ユーザー入力を起点とし、時間のかかるバックグラウンド処理を行いながら、ユーザーにはローディング表示などで進捗やその旨を伝え、処理の完了時に結果を用いて画面を更新する、などのケースである。

この場合、ReducerからActionを呼ばなければならなくなってしまう。これはFluxのルールに反し、逆にそれを破れば、Fluxから受けられるメリットの大部分を失ってしまう。

非同期的処理が問題なのではない

それに対するアンサーがredux-sagaやredux-thunk、redux-observableのはずで、これらは「非同期的処理をReduxでうまく扱う」ためのものではない (これらは同じようなものなので、以降はredux-sagaを例にあげて話す) 。

上記の例は同期的な処理でも同じ問題がある。ユーザー入力から、ビジネスロジックと画面反映への手続き的な処理の連続について、すべてをReducerの中でできるわけがない。

そもそもFluxはデータフローであって、コントロールフローやアプリケーションアーキテクチャではない。そんな勘違いはさっさと捨てるべきだと思う。

redux-sagaによるアンサー

つまるところ、先述のような「ユーザー入力から色んな処理を行い、画面反映する」を行わせる、間にある層」が必要なわけだ (以降、これを便宜上ユースケース層と呼ぶ) 。

redux-sagaはそれをReduxのミドルウェアとして実装している。sagaと呼ばれるユースケース層を独自的なDSLによって、手続き的な処理を宣言的に書いていく。

(起点がActionで、sagaのコントロールフローで画面反映のためにReducerにディスパッチするのもActionなので、やや気持ち悪い感じはあるが)

他のアプローチ

作るWebアプリケーションの規模が一定以上になってくるなら、きちんとしたWebアプリケーションアーキテクチャを構築した方がいい。

アプリケーション内での値のうち概念化できるものはエンティティクラスにするべきだし、外界とのやり取りにはモックが可能なようにサービスクラスのようなものを設けた方がいい。

同じように、アプリケーションの機能をユーザーの目線で体系化すれば、自然とユースケース層が必要になる。この中で外界とのやり取りをPromiseで行い、画面反映のためにReducerにActionをディスパッチするのでもいい。

まとめ

Reactは透過的ユーザーインターフェースで、Fluxは有限オートマトンのように考えると、一種のパラダイムシフトのようなものを感じる。

Reduxとredux-sagaは切り離せないものではないし、中規模以上のWebアプリケーションにはredux-sagaなどは正直不向きだと思う。

「非同期処理をどうするか」に注目してしまうと全体が見えなくなりがちだけど、つまるところ「ユースケース層」のようなものは必要ですよね、となるはずなんだけど、どうもそういう話があがってこないので燃料として書いた。

--

--

Kohei

https://axross.dev/ Clean coder who loves Flutter, Deno, React, TypeScript and Firebase and enjoys Texas Hold’em Poker. カナダ在住のプログラマー・UIデザイナーです