Redux - Style Guide
絶対に守ること
state を改変しない
- あらゆるバグのもとになる
redux-immutable-state-invariant
やimmer
などのツールを活用せよ
reducers に side effect を記載しない
- reducer の結果は state と action にのみ依存すること
- 下記のようなものを reducer に記載しないこと
- ajax, timeout, promise などの非同期処理
- Date.now()などの乱数生成
シリアライズ出来ない値を state や action に含めない
- Promise, Symbols, Maps/Sets, Functions, Class instances など
- ただし reducer に届くまでに
redux-thunk
などのミドルウェアで処理される場合は除く
複数の store を作らない
強い推奨事項
Redux Toolkit を使え
ベストプラクティスが詰まっている
immer を使え
rtk に含まれているよ
フォルダ構成は Feature Folders か、少なくとも Ducks にせよ
- ファイルの種類(reducers, actions, etc)でフォルダ分けするな
- Feature Folders --- 特定の機能に関する全てのファイルを一つのフォルダにまとめる
- Ducks --- 特定の機能のうち redux に関する部分のみを一つのフォルダにまとめる
なるべく多くのロジックを reducers に集約せよ
- action の発出側(コンポーネント)ではなく、reducer 側に積極的にロジックを記載せよ
- これによりテスト性やデバッグの容易さが高まるうえ、ミスも減る
- 発出側でしか行えない処理もあるが、最小限におさえること。
- e.g. id の採番など
reducer で state の構造をしっかり管理せよ
- Reducer の責務
- 初期値を決定する
- state を正しい形式に維持する(必要に応じて action.payload のバリデーションを行うなど)
- blind spread
return {...state, ...action.payload}
や、blind returnreturn action.payload
は避けること。- これは state の形式を決定する責務が action 発出側に漏れ出している状態であるため
- ただし、フォーム内のデータを編集する際は使って良いかも
- 要素ごとに action creators を書くのは時間の無駄だから
- 利便性の観点から、単一の root reducer ではなく、複数の slice reducer で state を管理すると良い
state の slice 名は格納するデータの名前にせよ
{users: {}, posts: {}}
がよい{usersReducer: {}, postsReducer: {}}
はだめ
state は データの種類により分けろ(UI で分けない)
- なぜなら、UI と、UI が必要とするデータは、必ずしも 1 対 1 で結びつかないので
- e.g.
auth
,posts
,users
,ui
- 上記の
auth
等は複数の画面からアクセスされる - UI ごとの state は
ui
配下に配置するとよい
- 上記の
- (これは議論の余地がありそう)
reducer を state machines として扱え
- 通常は action だけを見て無条件で値を更新しがちだが、これはバグを生みやすい
- state を考慮しながら、値を更新するかどうかを決定するとよい(これを state machine という)
- 例えば、読み込み状態が'loading'なのに新しいデータが飛んできた場合は無視する、など
ネストしたデータは正規化して管理せよ
- 簡単にルックアップできる
- 更新が簡単
- 良いパフォーマンス
state は最小限にせよ、必要なら派生データを作れ
- e.g. filtered lists or summing up values
- そうすれば
- state が読みやすくなる
- 派生データを作るためのロジックを減らせる
- 派生データを多くの箇所で使った時に、同期が容易になる
- オリジナル state やその参照を、変更せずに残すことができる
action はイベントとして扱え。セッターとして使うな。
- 「何が起こったか」で命名する
action.payload === 'user/nameUpdated'
- 意味がわかりやすい
- dispatch される action の数が減る
- ログが見やすい
- セッ ターとして命名しない
action.payload === 'user/setNewName'
意味のあるアクション名にする
SET_DATA
やUPDATE_STORE
といった無意味な名前にしない
単一のアクションに複数の reducer が反応する構成を検討する
- スケールしやすい
- 発出すべき action の数を減らせる
複数の action を順番に発出しない
- なぜダメか
- ハイコストな UI アップデートを引き起こす
- action と action の中間で、おかしな state になる可能性がある
- なるべく一発の action で全てを更新せよ
- どうしても必要な場合は
react-redux
のbatch()
を使え(React18 では不要)batch(() => {
dispatch(increment());
dispatch(increment());
});
値を store に保存すべきか常に考える
下記の場合は store で管理するとよい
- 別のコンポーネントでその値が必要
- その値から派生した別の値を生成する必要
- 複数のコンポーネントで同じデータが使用されている
- タイムトラベルデバッグが必要
- キャッシュ機能が必要
- Hot-reloading されたときに値を保持する必要がある