以前久しぶりにReact教育のテキストをアップデートしたが大変だったというブログを書きましたが、ステート管理の章は完全に書きかえました。
昔はReactのステート管理と言えばRedux一強だったので、以前のテキストでもReduxの進化バージョンであるRedux Toolkitを導入してみる章になっていました。
しかし、現在はReduxブームは去りいろいろなステート管理ライブラリーが出てきました。そこで今回のテキストではステート管理ライブラリーを分類し紹介するような内容に書きかえました。
このブログにもテキストのステート管理ライブラリーの要約を書いてみます。
Bing Image Creatorが生成した画像を使っています
ステート管理ライブラリーの分類
ステート管理ライブラリーを以下のように分類してみました。
- 基本のuseState
- Redux型ライブラリー
- Recoil型ライブラリー
- バックエンドで管理
- その他ライブラリー
useSateはReactの基本なので説明は不要と思います。また、Recoilは最近開発が滞っているようなので「Recoil型」は今ひとつな分類名かもしれませんが最初に現れた、このスタイルのライブラリーなので敬意を表て使うことにしました。
2. Redux型ライブラリー
大規模なアプリを作るにはコンポーネントとは別に、ステートを集中管理する専用ライブラリーが必要だという考えで生まれたのがReduxです。
一時期は熱狂的に支持されましたが、元祖Reduxは異様にたくさんのコードを書かないといけないので、やや下火になりました。
Reduxのアーキテクチャ
- Single source of truth : アプリケーションの持つすべてのステートを単一のツリー構造でstoreオブジェクトに格納する
- State is read-only : ステートの変更を起こすのは、 更新情報を持つActionだけです
- Changes are made with pure functions : ステートの変更は、変更手順が記述された純粋関数のreducerによって行われます
Reduxのデータの流れ
- ユーザーがボタンをクリックするなど、アプリ内で何かが起こる
- アプリのコードは、
dispatch({type: 'counter/increment'})
のようにReduxストアにアクションをディスパッチします - ストアは以前の状態と現在のアクションでreducer関数を再度実行し、戻り値を新しい状態として保存します
- ストアは、サブスクライブしているUIのすべての部分に、ストアが更新されたことを通知します
- ストアからデータを必要とする各UIコンポーネントは、必要とするステートの部分が変更されたかどうかをチェックする
- データが変更されたことを確認した各コンポーネントは、新しいデータで再レンダリングを強制し、画面の表示内容を更新される
Reduxのメリット・デメリット
- メリット
- どのコンポーネントもステートを管理しているReduxと接続できるので、ステート等をバケツリレーで親コンポーネントから、子、孫と伝えなくても良い
- Reactコンポーネントからロジック的なコードを無くせる
- ルールが決まっているので、誰が書いても同じようなコードになりメンテナンス性が上がる
- 大規模・大人数プロジェクト向き
- アプリ内の状態はreducerを見れば判り、イベント処理はactionを見ればすべて判る
- デメリット
- ちょっとしたステートの管理とイベントの処理だけなのに、異様にたくさんのコードを書かないと行けない
- Reduxを使うコードはコンセプト/ルールに沿ったコードを正しく書かないと正しく動かない
Redux系ライブラリー
- Redux Toolkit Reduxの改良版、コードがかなり書きやすくなった
- zustand Redux ToolkitをさらにシンプルにしたRedux系ライブラリー
- useContex + useReducer Reactの標準HooksでRedux的なものが作れる
3. Recoil系
Atomという単位でグローバルなステートを管理でき、React標準のステート管理useStateを使うような感じで使えるシンプルなステート管理ライブラリーです。
- Minimal and Reactish : RecoilはReactのように動き、Reactのように考える。あなたのアプリに追加して、高速で柔軟な共有ステートを手に入れよう
- Data-Flow Graph : 派生データと非同期クエリは、純粋な関数と効率的なサブスクリプションによって制御される
- Cross-App Observation : 永続化、ルーティング、タイムトラベルデバッグ、またはアンドゥを実装し、コード分割を損なうことなく、アプリ全体のすべての状態変化を観察します
構成要素
- Atom : 管理するステートに名前を付け、初期値や型(TypeSciptの場合)を定義します
- Selector : ステートの値の取得・更新用時に派生データを定義する事ができます
- API :
- useRecoilState() : useState() 同様に、ステートの値と更新関数を戻す
- useRecoilValue() : ステートの値のみ戻す
- useSetRecoilState() : ステートの更新関数のみ戻す
- ・・・
Recoilのメリット・デメリット
- メリット
- 簡単にグローバルなステート管理ができる
- 関係ないコンポーネントが再描画されたりしない
- デメリット
- ロジックがコンポーネント内に書かれてしまう可能性がある
Recoil系ライブラリー
- Jotai Recoilをさらにシンプルにしたライブラリー
4. バックエンドで管理
Reactアプリではグローバルなデータ(ステート)を管理せず、データ(ステート)はバックエンド(サーバー)で管理する。
- 画面表示のステート等はuseStateなどで管理
- 通信ライブラリーのSWRやTanStack Query (旧React Query)、Apollo Clientはキャッシュを持っており、不用な通信量をへらすことができる
バックエンド管理のメリット・デメリット
- メリット
- Reactアプリがシンプルになる
- デメリット
- 通信が遅い、不安定な環境では使いにくいかも
5. その他
他にもステート管理のライブラリーはあります。
- Mobx : オブザーバー・パターンを使ったステート管理、そこそこ人気がある
- XState : 有限オートマトンを使ったステート管理、複雑な状態遷移を管理できる
まとめ
テキストでは、Redux型ではzustand, Recoil型ではJotaiを使ったサンプルコードで、さらに理解を深めるようにしています。
実際の開発でも複雑なグローバル・ステートを持つアプリであればzustand、シンプルなアプリならJotaiを選ぶと思います。
ただし、バックエンドで管理もキャッシュ等が上手くつかえれば魅力的かなと思います。さらにReact Server Components時代になるとクライアント側でのグローバル・ステート管理は減るのでしょうか?