React+Fluxアプリケーションのメンテをしている中で「propsのバケツリレーを減らすためにContainerを増やしてもよいか?」というディスカッションになったので、考察をまとめてみます。

いまの設計の確認

  • FluxUtilsフレームワークを利用している
  • 複数のStoreを持つ
  • ComponentTreeのRootをContainerとし、StoreのStateを受け取る
  • Tree状に配置された各Componentにはprops経由で状態を受け渡す
  • 各Componentはローカルステートを持つことができる

ちなみに、ここで言うFluxの定義については「React+Fluxで正しく設計するためのFlux見直しガイド」にてまとめています。

propsのバケツリレーと単一Containerとは?

Reactアプリケーションでしばしばある「ComponentTreeのRootでアプリケーション全体のStateを受け取り、それを何階層もの子Componentにprops経由で受け渡していく」という設計を、ここでは単一Containerpropsのバケツリレーと呼んでいます。

これらの手法をなんとなくお作法だと思いこんでいましたが、本当にそうなのでしょうか? もっと良い設計があるのではないか、というのがディスカッションの種でした。

propsのバケツリレーの生い立ちを想像してみる

ざっくり言うと、ReactはComoponentを「propsを受け取り、それに応じた描画を返すもの」と定義しています。そしてComponentをTree状に並べることで画面を構成する仕組みです。
これらをつなげると「Treeの上から下へ何階層もpropsを受け渡していく」ということになりそうです。連鎖的に「Treeの一番上でStoreからStateを受け取って」が枕詞になり、次第にReactの典型的な形となっていったのかもしれません。

公式ドキュメントの見解

React

In a typical React application, data is passed top-down (parent to child) via props

propsによる状態の受け渡しを「典型的」とは言っているものの、推奨している記述は見つかりませんでした。

Flux

単一Containerとすることを推奨する記述はみつからず、むしろ「Containers」と複数形で表現されていました。

Redux

Most of the components we’ll write will be presentational, but we’ll need to generate a few container components to connect them to the Redux store. This and the design brief below do not imply container components must be near the top of the component tree.

「いくつかのContainerをつくる」「TreeのRoot近くにContainerを置く義務はない」との記述がありました。

まとめると、執筆時点の公式ドキュメントを見る限り、どれをとってもpropsのバケツリレーや単一Containerを推奨する記述はありませんでした。
ReduxはStateをひとつのオブジェクトとして扱うので、もしかしたらそれを受け取るContainerもひとつなのではと思いましたが、そんなことありませんでした。

私の見解 最適解を探すこと

ここまでの内容から思いつくことを箇条書きしてみます。

  • 単一Containerや、壮大なpropsバケツリレーは必須ではない
  • ただ、アプリケーションの性質によってはそれらを使って実装しても問題ない
  • 「典型的」であることは、構造を理解しやすい利点もある
  • 単一Containerには「Storeアクセスを1箇所にまとめる」利点もある
  • 単一Containerを使わなくても「Container Component」と「Presentation Component」を分離した設計は実現できる
  • 単一Containerを使わなければ、個々のComponent毎でデータ取得から描画までの一連の処理を完結させられる
  • 既にある設計を覆す場合は、どうやって新しい設計に置き換えていくかのマイルストーンとセットで考えたい
  • 一部分だけ別の設計にすると混乱の元なので、全部変更しないか全部変えるかの二択で考えたい

一言で言えば、単一Containerやpropsバケツリレーにもメリットはあるので、良し悪しではなくあらゆる手法の中でどれが最適かを考えていきたいということに尽きますね。

その他の見解 テスタビリティから考える

ディスカッションの中で出た面白い着眼点に「どちらがテストを書きやすいか」というものがありました。

単一Containerであれば、上述の通りStoreにアクセスするロジックを1箇所にまとめられます。その結果、RootComponentはStoreアクセスに関するテストを、それ以外のComponentはUIに関するテストを書くことに専念できそうです。

逆に各ComponentをContainerとしStoreアクセスから描画までの一連の処理をComponentに閉じ込めれば、Component間の依存度が下がり、独立したComponentとして各々が必要な機能のテストを書けそうです。

どちらがテストを書きやすいかは一概には言えず、アプリケーションの性質に依存しそうです。たとえばキュレーションサイトのような情報を表示することに特化したアプリケーションであれば、前者のほうがマッチするかもしれません。管理画面のようにユーザの入力とStoreの状態を結びつけることが多いアプリケーションであれば、後者のほうがメリットが大きくなりそうですね。

アプリケーションの性質によってComponentの役割の与え方が代わり、それによりどういった設計やテストが必要かなどが変わってくるということですね。

まとめとしてタイトルの「propsバケツリレーは必要か」に回答するなら、「必要か否かの二元論ではなく、アプリケーションの性質次第では役立つ場面もある」ということでしょう。曖昧な結論になってしまいましたが、単一Containerやpropsバケツリレーという手法の良し悪しだけに着目するのではなく、アプリケーションの性質を踏まえて考えると見え方が変わってくるというお話でした。

現場からは以上です。