お気持ち
redux は面倒くさい。
コンセプトは分かる。
データの流れが一方向になるのは素晴らしいし、state の更新は常に純粋な関数で行われる。
そこに副作用は無いので分かりやすい。
reducer は関心のある UI イベントだけを知っていれば良いし、UI は状態がどう更新されるのか知らずに唯ひたすらイベントにフックして action を dispatch するだけでよい。
state 自身も分割統治されており、またライブラリ製作者も用意に state に状態を持って action を dispatch することが出来る。
これによってライブラリとプロダクトコードのインテグレーションも用意になっている。
発行されるアクションは action types で一覧化されているのでそこを参照すればアプリケーションで dispatch される全ての action が知れるようになっているので意図せず同じ action 名で別のアクションを作ってしまうことも無い。
わかる。でもつらい。
あまりに定型的なコードを書く量が多すぎる。
やっていることは UI と 状態を結びつける pub/sub で、redux のドキュメントでも action は操作単位ではなくイベント単位で発行されるべきというような事が言われている。
結局 redux は__イベントに基づいたアーキテクチャに flux の一方向性を組み込んだもの__というだけだ。
超が付く程の大規模なコードであればイベントベースというのは理想的には UI はイベントを発行するだけで良く、ロジックが変更になっても UI 側のコードには手が入らないという恩恵は大きいかもしれない。
しかし実際にコードを書いているとどのタイミングで state が変更されるのかというのは結局意識したい事が多くて (自分がイベントベースに染まれていないだけかもしれないが)、どの action とどの reducer が対応しているのかというのを確認したりしている。
そのその時点で既に redux の恩恵を受けれていない。
イベントの購読者が発行者の事情を知らなくて良いと言うのは理想だが、結局私たちはイベントベースであってもイベントと操作をほぼ一対一で考えてしまうし、それは結局__イベントベースではなく操作ベースで処理を行っている__のと同等である。
もはや redux でなくても良い。
とはいえ redux のエコシステムは十分に成熟しているし、その恩恵を受けたいという気持ちもある (特に開発者ツールやミドルウェア)。
ducks パターンは action と reducer が一対一、一対多であっても同じ関心・リソース内で完結しようとしており、そのような割り切りの上でお互いに密結合になりがちな action types, action, action creator, reducer を纏めてしようとしている。
ducks パターンは結局規約のみだが、なかなか上手く機能している様に思える。
完全なイベントベースでない場合、ducks パターンは見通しも良くなるが結局書く量は減らずまだ悶々とする日々を送ることになる。
昔話
多数の flux 実装が出てきて戦争が起きていた時 (実際は redux が勝利を収めつつある時だが) に baobab-react という flux 実装を見つけて、それが筋が良いように思えたので使っていた。
baobab-reactを使って軽量fluxを回す
これはシンプルだがなかなか上手く機能していた。
やっている事を redux で言い換えると、tree というアプリケーションの全ての state を保持する単一の store があり、action という reducer の switch case 句 1 つに相当するものがあり、dispatch があるという感じだ。
action が reducer の switch case 句 1 つに相当しているというのが重要で、これは state に対する変更に対して名前を付けているのと同じになる。
そう、これは redux のイベントベースの flux とは異なる操作ベースの flux だった。
会社の製品のコードをこれで回したことがあったが、操作ベースというのが開発者の理解も得られやすくすんなりと受け入れられたように思う。
問題といえば redux のようなエコシステムが無いので baobab-react と上手く連携できる form ライブラリのようなものは当然存在せず (今なら formik があるが)、フォーム周りで扱う状態が山のようにあって大変だった事くらいだ。
実運用上で baobab-react が持つ操作ベースというところでの問題は発生していなかったように思う。
結局、自分はどうしたのか
自分が求めているのは redux のエコシステムに乗りつつも操作ベースの flux で簡潔に書けるもので、
新しい状態管理のライブラリが出ては baobab-react の影を追い続けていただけだった。
そしてそれに相当して納得できるようなものは今日まで現れてくれなかった。
ならば作るのみと書いたのが redux-cirquit になる。
これは操作ベースの短絡 redux を実現する。
createCirquitAction
が受け取るのは store の state を変化させる関数 (簡便のため reducer と言ってしまう) で、
createCirquitAction 返すのはごく普通の redux action なので普通に dispatch することが出来る。
reducer が一操作の単位となるので、createCirquitAction によって action を返す関数に対して名前を付けることで操作に名前を付けることができるようになる。
状態を操作する側は操作したい物だけを使えば状態を意図したとおりに変化させることが出来るし、コードも処理を書き下すように書くことが出来るようになる。
1 つ難点を挙げるとすれば、reducer が全ての状態を受け取ってしまうので一部を更新するのが手間な所だ。
これは別に redux-cirquit に限らず複雑なオブジェクト等をイミュータブルに変化させる時に発生する問題だが、
それも immer と組み合わせることで解決できると思っている。
immer は proxy ベースの状態操作ライブラリで、正にこのような問題を解決するために生まれた。
詳しくは immer の readme を参照してほしいが、immer を使うことでイミュータブルな状態の操作が手続き的かつミュータブルに書くことが出来るようになる。
これで毎回 {...state, hoge: {...state.hoge, なんたら}}
の様なコードを書かなくて済むようになる。
(因みに baobab-react は baobab によってそのあたりを上手く書けるようになっている)
redux-cirquit を使う際には immer の併用をご検討頂きたく。
まとめ
- redux はイベントベースの flux 実装を行った
- 人は、操作ベースの redux をやりがち
- 操作ベースになると redux が細かく要素を分割している旨みが無くなる
- ので、操作ベースの flux を求めていた
- redux 上で動く良い感じのものが無かったので redux-cirquit を作った
- redux-cirquit は redux 上で無理なく操作ベースの flux をやる為のライブラリ
ポエムを書く (というか長文を書く) のは苦手だと思っていたけど redux について悶々としすぎてお気持ち多めでお送りしてしまった。
わかる〜と思ったら redux-cirquit にスターください。