dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

"Redux Architecture Guidelines"を読んだ

こちらの記事を読んで。React&Redux でアプリを作る上で基本的だが重要なことが書かれていたので、後で見返すためにメモ。

なお本文は原文記事の完全な翻訳ではなくポイントだけかいつまんで自分の考えも交えたものです。
また、まとめさせていただく上で著者の許諾を得ています。
(拙い英語でおそるおそる訪ねたら流暢な日本語で快諾いただいてびっくり)


State

state の形を計画(設計)する(Plan your state shape

state オブジェクトの構造をきちんと設計することは非常に重要。
こういった構造にすればいい!という唯一解はないが、設計の手助けとなるチェックリスト(質問リスト)がこちら。

  • users, accounts, items など、API 経由で取得した複数のリソースか(配列かどうか、の意?)
  • ローディングアイコンを出す/出さないなど、loading state を扱うか
  • 成功またはエラー時の通知 UI を扱うか
  • 一貫性がありかつ予測しやすいか。別のチームメンバーがすぐに理解できるか
  • 必要なデータにアクセスしやすいか。不必要にネストしていないか
  • serializable か。ローカルストレージやデータベースに簡単に保存できるようになっているか
  • state のかわりに URL でアクセスできるプロパティはないか
    • URL でアクセスできる = state に保存する必要はない?
  • 重複したデータはないか


state の過剰なネストは避ける(Avoid nesting state objects

こういった state 構造になっていることがたまにある。

{
  foo: {
    bar: {
      baz: {
        qux: ...
      }
    }
  }
}

リレーショナルデータをそのまま state に突っ込むとこういった深いネスト構造になりがちだが、深いネストはそれだけ複雑さを上げる要因となる。

  • コンポーネントレベルでは、欲しい情報を得るためにネストをたどる必要がある
    • これは適宜 container component にすることでいくらか解決しそうだけど
  • reducer レベルでは、state 更新の際のマージ作業が複雑になる
  • 加えてパフォーマンスにも影響する

Normalizing State Shape · Redux
記事中でもリンク貼られてた Redux の公式ドキュメント。知らなかった。

これは今読んでる The Complete Redux Book にも書いてあった。


raw data だけ state に保存する(Storing only raw data in the state

記事中では Redux アプリにおける data には2種類あるとしていて、

  1. raw data:アプリが必要とするデータ。API 叩いて fetch したデータなど
  2. derived data:raw data から計算することで得られるデータ
    • firstNamelastName からユーザーの Name を得る、のような

後者の derived data は raw data からいつでも導出できるので、わざわざ state に保存する必要はない。
state に何か新しい情報を追加するときは、「これって現在の state の情報から作れない?」というのを自問してみるといいとのこと。


React の state よりも Redux の state を使う(Prefer Redux state over React state

React にも state 管理のしくみはあるが、 state の大部分は Redux の state として持たせた方が一貫性があって良い。特にチーム作業においては。

とはいえ例外として React の state で管理した方がいいケースもある。たとえば、複雑な UI の状態など、アプリ全体には重要でないような state を管理するようなとき。


Actions

action の payload は標準化する(Standardize action payloads

特にチーム開発においては、action の型というものも統一しておいた方がいいよねという話。
私も使っているけど Flux Standard Action に従っておけば大体問題ないんじゃないかな。


Action は composable にする(Ensure action creators are composable

composable は「組み立て可能な」ぐらいの意味?
既存の Action を組み合わせたより複雑な Action を作るというケースが実際のプロダクトだとよくあり、そのために Action のインターフェースを統一しておくと良い。
著者は全部 Promise でラップするようにしていて、そうすると全ての Action は then でつなげられる。

redux-thunk 使ってるとそんなに意識することがない?


Component Architecture

Containers & presentational components

コンポーネントの見た目に関する責務を持つ presentational component(純粋な React コンポーネント)と、コンポーネントの振る舞い?に関する責務を持つ container component(connect() したコンポーネント)を明確に分ける。

container と presentational という用語、公式ドキュメントにちゃんと書いてあったの知らなかった。
Usage with React · Redux


コンポーネントツリーの中間でも適切に container を使う(Use intermediary containers

コンポーネントツリーのどこを container にするかはアプリ作ってると悩まされる問題だけど、container にしていいのはアプリの root コンポーネントだけという制約はなくて、むしろそうすると末端のコンポーネントに必要な props を中間のコンポーネントがひたすら "passing through" するだけの無駄が発生する。

こういった不吉な臭いを感じ取ったら適宜 container 化していくといいんじゃないか。

Container にすることによるメリット・デメリットは kuy さんのこの記事が大変わかりやすい。

簡単に言うと connect() した container コンポーネントは Redux に依存することになるので、純粋な UI パーツとして再利用可能なコンポーネントにはならなくなる。そのトレードオフで判断するといい。