React Context のセットアップ方法

Prop drilling(プロップ・ドリリング)はコードを台無しにします。必要のない5層ものコンポーネントを経由してデータを渡すことになってしまうからです。React Context はこれを解決してくれます。しかし、多くのセットアップ方法では、新たな問題が発生します。

コンテキストフックに対して「定義へ移動(Go to Definition)」を使用すると、多くの場合、小さな useContext の呼び出ししかないファイルに飛ばされてしまいます。実際のロジックは別のファイルにあるからです。その結果、状態(state)のロジックを探し回るだけで一日が終わってしまうこともあります。

より良い構造を採用することで、これを解決できます。

以下のパターンに従ってください:

  • コンテキスト、公開フック、および状態ロジックを1つのファイルにまとめます。
  • Provider は薄いシェル(thin shell)として保ちます。
  • Lint ルールを使用して、プライベートなロジックを保護します。

ファイル構成は以下のようになります:

src/context/ • GameContext.ts(コンテキスト、公開フック、および状態ロジック) • GameProvider.tsx(薄いシェル)

GameContext.ts の内部では、次の3つのパートを作成します:

  1. コンテキストオブジェクト。
  2. コンポーネントが使用するための公開フック。このフックは、コンテキストが null の場合にエラーをスローするようにします。これにより、コンポーネントが Provider の外にある場合に、早期にエラーを検知(fail fast)できます。
  3. 状態管理のためのプライベートフック。このフックに useStateuseReducer の呼び出しを含めます。

公開フックとプライベートロジックを同じファイルに配置することで、「定義へ移動」が完璧に機能します。コンポーネントからロジックへ直接ジャンプできるようになります。

ただし、1つリスクがあります。プライベートフックは Provider で使用するためにエクスポートされるため、チームメイトが誤った場所でそれを呼び出してしまう可能性があるからです。もし呼び出してしまうと、共有コンテキストを使用する代わりに、新しく孤立した状態を作成してしまいます。

これを防ぐためにコメントを使うのはやめましょう。代わりに ESLint ルールを使用します。

私はこの問題を解決するために eslint-plugin-restrict-callers を作成しました。これを使えば、どの関数が特定のフックを呼び出すことを許可するかを定義できます。

例えば、useGameStateManagement を呼び出せるのは GameProvider だけであると ESLint に指示できます。他の誰かが呼び出そうとすると、明確なエラーメッセージとともにビルドが失敗します。

ワークフローのまとめ:

  • コンテキスト、公開フック、プライベートロジックを1つのファイルにグループ化する。
  • Provider は、フックの結果を provider の値に渡すためだけに利用する。
  • コンテキストが欠落している場合は、公開フック内でエラーをスローする。
  • ESLint を使用して、公開フックとプライベートフックの境界を強制する。

出典: https://dev.to/jareddlewis/how-to-set-up-react-context-2c9h