React 状態管理ツールの沼にハマった話
2020年に、株式会社パルケのCTOに就任してから、Reactでいくつかのサービスを開発してきました。
状態管理ツールの選定に悩みに悩み、試しに試してブレまくってしまい、プロダクトでどのように状態管理ツールを使っていくのか混沌としてしまっていた時期がありました。
その時期にプロダクト開発に携わっていただいて、いろいろ混乱させてしまった開発メンバーの方には、この場を借りてお詫びしたいと思います。
なお、この記事は当時の個人の体験・印象に基づくものであり、事実とは異なる場合、現状とは異なる場合があります事をご了承ください。
状態管理ツールに求める事
コンポーネント間のデータの受け渡しをPropsを介在せずにできる事、データの変更に応じて関連するコンポーネントの再描画する事の2点だと思います。
これらはどの状態管理ツールでも問題なく実現できますので、選定にあたってはそれ以外の開発体験(という開発者の好み)という部分になります。
- 都度公式サイトなどでソースコードを参照しなくても、記憶で書けるぐらい簡単か
- エディタのインテリセンスの恩恵が十分に受けられるか
- テストが書いたりデバッグがしやすいか
- プロダクトがスケール(肥大化)した時に破綻しにくいか
開発者のその時点でのスキルや経験、好みに左右され事が、沼化しやすい要因でしょう。
私の状態管理ツールの変遷
状態管理ツールについては2年間ずっと頭から離れず、次々と手を出しては浮気してみたり戻ってみたりと節操がありませんでした。
Redux Toolkit期
最初に利用した状態管理ツールです。この時点では他の選択肢は知りませんでした。
ReduxよりはRedux Toolkitの方が少ないボイラプレートで書きやすいんだな、という程度の知識しかありませんでした。
当時は状態管理ツールの使いこなせず、Reducerにいたっては必要性すら理解できていませんでした。
単純に記述しないといけないコード量から面倒さが勝って、結局Propsでデータを受け渡す方法を採用していました。
Recoil期
しばらく経ってから、Recoilの存在を知りました。
これなら見覚えのあるHookと同で書きやすそうだ、という事でRedux Toolkitから乗り換えました。
これは素晴らしい、と思い活用しまくったところいくつか問題が出てきました。
- 文字列でキーの名前をつける必要がありますが、どうもこのキーが重複させてしまいそうで落ち着きません。別でキーどこかで一限管理しないとなあ、という悩みが生まれました。
- Atomの値をsetで更新し、その変更に伴う処理でuseEffectを多用してしまっていました。これにより、setとuseEffectがすぐにスパゲティ化してソースコードの可読性が著しく悪化し、メンテナンスが困難になりました。この体験により、Reducerの必要性が少し理解できました。
SWRまたはReact Queryに全部寄せればいいのでは期
当時利用していたFirebaseからのデータフェッチのキャッシュでは、SWRを利用していました。ここにもデータをキャッシュしておく仕組みがあるので、そこに全部寄せればいいのでは、と思いました。
ただ、ここでもキャッシュのキーをどう管理するか、という点が頭をもたげて本格採用には至りませんでした。
この頃は、データとそのデータの変更に伴う処理をコンポーネントから切り離してReduxのように一元管理した方がいいのではないか、と思い始めていました。
React Context期
そんな中、kent C Doddsさんの記事が目にとまりました。
ReactのContextで十分か、と目から鱗が落ちる思いでした。
useReducerを組み合わせる事で、データと処理をまとめる事ができる、Contextをコンポーネントツリーで必要な場所に差し込む事で、スコープを狭める事ができる、という点も良いように思いました。
Reactの標準に備わっている機能で十分、追加ライブラリ不要、と鼻息荒かったのですが、しばらく使ってみていくつか不満が出てきました。
- useReducerを組み合わせると書き方が複雑で、毎回サンプルコードやKentさんのページを参照しないと書き方を思い出せない
- 不要なレンダリングを防ぐために、コンテキストの作成、Providerの導入箇所に悩んでしまう
- 多用すると、Providerのネストが深くなってしまう
これなら大人しくReduxを使えばいいのでは、と思い始めました。
Redux Toolkit 再び
ReduxチームからRTK Queryがリリースされた事で再度Redux toolkitが注目を集めており、もう一度Redux Toolkit試してみようと思いました。
実際に使ってみて、Devtoolは非常に便利ですし、Reducerはテストが簡単に書けますし、安心のReduxだし、という事でこれで鉄板のように思えてきました。
プロダクトのコードも徐々にRedux toolkitに移行していきました。
多少ボイラープレートが増える事、AsyncThunkで非同期処理を書くのが少し面倒、という以外には大きな不満はありませんでした。
Zustandに出会うまでは・・・
Zustand期
このあたりで、私は完全に状態管理ツールの沼にハマっている事を十分に自覚していました。
それでもアレもコレもと試してみたくてしょうがありません。
React Queryでグローバルステート管理についてのガイドを穴が開くほど読み込んでいたところ、Zustandという見慣れない状態管理ツールの名前が目に留まりました。
早速調べて試してみると、非常に簡潔に直感的に記載ができ、すぐに虜になりました。
Poimandresというチームが開発されており、特徴の違う3つのツールが用意されています。
日本人のdai-shi さんという方がコア開発メンバーという事で親近感が湧きました。
Reduxの置き換えを狙っているようで、Redux ToolkitからZustandへの移行はスムーズにできました。
ちょうどそのころ、dai-shiさんがReactの状態管理に関する本を出版されていたので、早速読んでみました。
JotaiについてはRecoilに似ているという事もあって距離を置いていたのですが、この本を読んでトライしてみたくなりました。
Jotai期
Jotaiでは、Recoilで感じていた問題が見事に解決されていました。
- Atomのためのキーを決める必要がない
- 読み取り専用Atom、更新専用Atomというのがあり、Reducerのような複雑処理もAtomに押し込む事ができる。
- 結果的に、コンポーネントにはビジネスロジックが殆ど記載しなくてよくなる
パルケミートの開発ではJotaiをフル活用しました。
Peer To Peerで必要な情報の管理と相性が良く、気づくと全てがJotaiのAtomになっていく、、という不思議な体験をしました。
かなり満足度は高かったのですが、Jotaiだけで全てをこなすにはいくつかやりにくい点があり、結果的にZustandと併用する事になりました。
- 認証処理など、複雑な状態遷移を伴うものはJotaiの細かいAtom群だと制御が難しくなる
- 外部のツールで、どうしてもReactのコンポーネントツリーの外側で状態が必要になった時に、Jotaiでは情報の受け渡しが複雑になる。
Zustandに戻ってきた期
現在、パルケミートの開発はひと段落して、パルケトークの開発に注力しています。
そこでは状態管理はZustandをのみを採用しており、現時点でJotaiは導入していません。
Jotai、Zustandはそれぞれいいところがあって、Jotaiのお手軽さは本当に好きなのですが、どれかひとつだけ利用するとしたらReactの外でも使えるZustandが良いかなと思っています。
状態管理ツールのそれぞれの長所を生かすメリットより、複数使う事による複雑さの増加のデメリットの方が少し上回っているかなという印象です。
状態管理ツールの今後
SPAでアプリ開発するにあたっては、状態管理ツールに求められる事も多岐に渡ると思います。
一方で、昨今ではNext.jsやRemix.run、DenoのFresh(https://fresh.deno.dev/docs/introduction)のように、処理をサーバーサイド(エッジ)に回帰しつつあるように思います。
そうなると、状態管理ツールはJotaiのようにシンプル軽量で使いやすいもので十分となり、React QueryやURQLのようなフェッチデータのキャッシュ管理ツールの方が重視されていくのかもしれません。
(2022年10月7日追記)
このあと、またJotaiに戻っています。
そのくだりをツイートしました
Discussion