状態管理ライブラリはなぜ必要? 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)
と書いたとき、内部的には
- store.subscribeに登録
- stateが変わる度にselecter(state)を実行
- 前回の結果と比較
- 差分があったら再レンダリング
ということをやっています。
何が嬉しいのか
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