🐕

状態管理ライブラリはなぜ必要? Redux再入門編

に公開

そもそも「状態」とは

Reactでは「状態(State)」はこれです。

const [count, setCount] = useState(0);

「状態」はUIの出力を一意に決定するためにフレームワークが管理する変化しうるデータです。
状態が変化するとフレームワークはUIを更新します(再レンダリングします)。

ReactやVueのUI(DOMの仮想表現)を「状態を受け取る純粋関数的な変換」として捉えてみると、
UI=f(state)
と理解できます。
つまり、入力となる「状態(state)」が変化するとそれに伴い出力UIも変化します。
フレームワークはこの動作を組み合わせてアプリを構築します。

Context APIを使えばいいのでは?

一緒に見ていきましょう。

<UserContext.Provider value={user}>
  <App />
</User.Context.Provider>

これでグローバル状態[1]っぽくなりますね。
しかし、以下のような問題が発生してしまいます。

問題1:描画効率の低下

Contextの値が1個変わる

使っている全コンポーネントが再描画
となり、効率が悪くなります。

問題2:更新ロジックが分散する

  • どこで変更されている?
  • なぜ変更された?
  • いつ変更された?

を追跡するのが困難になります。

Reduxの登場

Reduxは簡単に言うと、状態遷移を"イベントログ"として管理するシステムです。

状態は1つの巨大なツリー(plain object)で、更新は「action」起点でしか起きません。
状態は原則immutableで変更の理由はactionに残ります。
つまり状態遷移の履歴を残すわけです。

Reduxのコアとなる思想は

「状態は1箇所にまとめて、変更ルールも1つにする」

です。

  • 状態はstoreに集約する
  • 状態変更はaction→reducerだけで行う
  • UIは状態をみるだけ

Reduxでは、UI→dispatch(action)→reducer(state, action)→new state→UIを守りながらUIを更新していきます。

storeとは、状態(state)と、それを変更するための唯一の入口(dispatch)と、状態変化を通知する仕組み(subacribe)をまとめたオブジェクトです
中身は、現在のstateを返す関数getState()、純粋な状態遷移ルールを記述したreducerに状態をどう変えるか(action)を伝えるdispatch、dispatch時に「状態が変わりました」と通知するsubscribeの3つセットにすぎません。

具体例を見てみましょう。

// action
{ type: "ADD_TODO", payload: "studying" }

// reducer
function todoReducer(state, action) {
  switch (action.type) {
    case "ADD_TODO":
      return [...state, action.payload]
    }
}

UIはただ

dispatch({ type: "ADD_TODO", payload: "studying" })

をするだけでいい。

「stateはimmutable」を強制する文化が効いていますよね。
もしmutableだったら、参照先が同じになるので差分が0と判断され再レンダリングされません。

react-reduxでは

react-reduxでは

useSelecter(selecter)

と書いたとき、内部的には

  1. store.subscribeに登録
  2. stateが変わる度にselecter(state)を実行
  3. 前回の結果と比較
  4. 差分があったら再レンダリング

ということをやっています。

何が嬉しいのか

1. 状態遷移が追える

ログを見れば「なぜいまこうなっているのか」が全部わかるので、デバッグが楽になります。

2. 予測可能性が高い

状態=f(過去の状態, action)

3. 非同期処理を制御できる

Redux Thunk/Sagaで

  • API通信
  • ローディング管理
  • エラーハンドリング
    を中央集権化できます。

Reduxのデメリット

上に書いたように便利なReduxなんですが、

  • 書く量が多すぎる
  • 小規模アプリだとオーバーエンジニアリング

などの点から嫌われてしまっています。。。

Redux Toolkitの登場

Redux Toolkitでは

const slice = createSlice({
  name: "count",
  initialState: 0,
  reducers: {
    increment: state => state + 1
  }
})

これを書くだけで

  • immutable処理→内部でimmer
  • action自動生成
  • reducerの統合
    をやってくれます。
    楽ですね!

Reduxを使うべき場面/使わなくていい場面

使うべき場面

  • グローバル状態が多い
  • 複数画面で同じ状態を使う
  • 非同期処理が多い
  • 状態遷移の可視化がしたい

使わなくていい場面

  • 小さいSPA
  • フォーム中心
  • 状態が局所的

Reduxは「チーム開発、長期、複雑」で真価を発揮するのでZustand等で十分なケースが多いです!

次回のZustand編でまたお会いしましょう。

Discussion