Reactのprops/contextの使い分け

Reactのprops/contextの使い分け

仕事先でたまたまこれの話になり、個人的に思っていることをまとめた。 公開したのは、時々見かける「どっちを使うべき?」みたいな議論に 自分も混ざりたかった 思うところがあったから.

「とにかくpropsでいい」と自分は考えている。

なによりReactは書き方に詰まった場合に、フレームワークライブラリ固有の事情を考慮して解決するというよりも、実装や設計上の問題が一般的なプログラミングパターンの範疇の発想で解決できるのがよい

前提

以下のように考える

  • React/preact のコンポーネント = 通常のclassや関数
    • 状態を隠蔽して抽象する
    • 最近は冪等性がどうとかReact語るときにあんまりいわなくなったけども....
  • props = 関数やメソッドの引数(入力)
  • context = グローバル変数(モジュールグローバルな変数)

実装の指針

1. まず引数(props)でどうにかできないか考える

  • グローバル変数に依存しないクラス・関数の方が、テストもメンテナンスしやすい
  • 大抵の場合はpropsでどうにかなる

2. propsでは「めんどうくさい」「うまくいかない」と感じた時にグローバル変数(context)を、やむを得ず使う

以下のようなケースで、グローバル変数を使った方が良いのか一回落ち着いて考えた上で、それでもやっぱりグローバル変数(context)を使った方が良いなと思ったら使う

  • スケジュールの都合、propsよりもcontextを用いた方が簡単である
  • ページ(アプリケーション)中で同じ値を、必要な箇所で参照している
    • テーマ機能の実現
    • ページの初期化時に取得した認証情報を触る
  • propsで渡すと、どうにもコードが簡潔に書けない

注意点

  1. Contextを利用する場合, <Context.Provider/>で囲った箇所(ツリーの子孫)は、Contextの値が更新されると全て再実行される。そのため、性能問題が発生しやすいので、値が頻繁に更新されるものは、上の基準に合致していても、なるべくpropsで渡すようにする
    • そもそも自分はなるべく使わないのでそんなに困らんのだけどね.....
  2. Contextを利用する場合であっても、なるべくuseContextの呼び出し回数を最小限にする

コードレビューの指針

以下を見る:

  1. そのcontextの影響範囲・利用用途は適切か
    • 影響範囲が広い場合、注意深く検討する
  2. 気になるなら実装者に「propsに直してみるのはどうだろう?」と聞いてみる
    • 実装者に任せてみる
  3. 究極的には「どんなに”ひどい”設計・実装でも、影響範囲が小さい(使用箇所が限定されている)ならば、大抵は問題ない」ということを留意する
    • 困ったら、その時にリファクタしたり、丸々書き直せば良い
    • あとで修正しやすくするために、テストケースの有無や妥当性を検討する方がよい

Q&A

Q. propsでなるべく渡そうとすると、どうしてもpropsで受け取る値が増えてしまうけど、どうすればいい?

<Hello bar1 bar2 bar3 bar4 bar5 bar6 bar7 bar8 bar9/>

のように、たくさんの値が必要になってしまう場合. 以下のようなアプローチが考えられる

  • 「ひとつのコンポーネント(関数)で多くのことをやろうとしすぎている」ことが多いので、困難を分割する
  • 関連する値をオブジェクトに一度まとめて渡す
    • ValueObjectを作る....というか一般的なモデリングの話というか
  • render propsprops.chidlrenといった手法を試す

背景

そもそも自分はContextを使わない。

  • ライブラリや他人の書いたコードがそれを要求してくる
  • 設計上とスケジュールの制約上、どうしてもContextを用いた方が早く作れる
    • かつ、値がほとんど更新されない

場合くらいしか使うことも使った記憶もなく、自分で設計を決められる場合は

  • 全体に影響がある値は、ルートにStoreないしStateを用意する
    • 余裕があればgetter setterを別のインターフェースとして定義する
  • そしてそれをひたすらにバケツリレーする

スタイルでゴリゴリやってしまう。

グローバル変数のたとえもあるし、「選択という手間を省き、その分の思考のリソースをより根本的な設計を検討するのに注ぎ込みたい」意味もある。 また、常にpropsを用いることで、パフォーマンスやリファクタの問題をシンプルにする、という意味合いもある。

あと、自分がReactやWebクライアントサイドアプリケーションに関して一番熱心に考えていた頃はLegacy Context APIしかなかった結果が、このスタイルに自分を規定した気はする。あの頃の「機能としては存在するけどなるべく使うな」の趣を思い出せば、自然と基本はprops一択になる。

対して。現在のContext APIはだいぶよくできている。 しかしながら、グローバル変数の例えもあるし自分からは積極的に使うほどでもない、みたいな距離感。

そんな感じなので、正直、「Contextで困ったらプロファイラ使って測って遅いところ削ったり、必要に応じて抽象化のリファクタすりゃいいじゃん。別にReact専用の体系的なあれこれ要らないよHAHAHA」程度の気持ちだったところ、ケーススタディガイドラインとしては以下のような切り口もあると嬉しいのではないかと同僚からフィードバックをもらった。

たとえば

  • 巨大なContextは作るな分割しろ
  • 直接 useContext()は使うな、カスタムフックの裏に隠して最適化の余地を残せ

みたいなのあるじゃないですか、と。

そう言われると、ReactのContextで困るとそういう感じのことやることになるよなあ、てか職場を問わず毎年1回〜2回はそういう変更のコミット作ってたわ。確かにね。

ただ、それらは自分にとっての体系的な何かというよりは、一般的なプログラミングのグッドプラクティスや技法の域を出ず、React固有の何かとして特筆する気になれないのであった(というわけで、ガイドラインのContextベストプラクティスの章は彼に任せることにした)。