はじめに

React開発において、Page コンポーネントと UI部品 コンポーネントの責務が曖昧なまま実装されるケースは少なくありません。
具体的には以下のような構造がよく見られます。

  • ルーティングロジックを内包するUI部品
  • Page コンポーネントにフォーム・モーダル・通知などが密集
  • 再利用されることのない部品が別ファイル化されている

こうした構造はレビュー段階で早期に発見しなければ、将来的なリファクタ時に全体の構造依存を読み解く負担を増やします。

なぜ構造分離が必要か

Reactではコンポーネントを機能単位ではなく、責務単位で分離するのが基本方針です。
再利用性・テスト性・構造理解の観点から、以下のような責務分離が望まれます。

  • Pageコンポーネント:ルーティング単位のページ。データ取得、グローバルイベントハンドリングなど。
  • UI部品コンポーネント:見た目の一貫性を提供し、状態やイベントロジックを含まない構造が基本。
Pageとは

Pageとは、ルーティングごとに構成される「アプリケーションの1画面単位のルートコンポーネント」を指す。Next.jsやRemixのようなファイルベースルーティングでは pages ディレクトリ以下のファイルがPageとなる。

よくある曖昧な構造の例

PageにUI部品が混在している例
export function DashboardPage() {
  const [search, setSearch] = useState('');
  const [isOpen, setIsOpen] = useState(false);
  const user = useUser();

  return (
    <>
      <Header />
      <input value={search} onChange={e => setSearch(e.target.value)} />
      {isOpen && <UserModal user={user} onClose={() => setIsOpen(false)} />}
    </>
  );
}
@Reviewer
`DashboardPage` の責務がUI状態と構造で混在しているため、フォームやモーダルは専用コンポーネントに分離すべきです。

このように、状態とロジックが混ざった構造は再利用しづらく、読みづらくなります。

UMLで見える責務の混在

UML Diagram

この図のように、Pageが細かい部品や状態ロジックに直接依存している場合は、責務の集中と再利用性の欠如が懸念されます。

レビュー観点1:Pageが抱えすぎていないか

以下の観点でレビューを行いましょう:

  • グローバル状態の依存が多すぎないか
  • UI部品の状態やイベントロジックがPageに残っていないか
  • Pageが再利用性のないロジックで肥大化していないか
構造レビュー
@Reviewer: `DashboardPage` に集中しているロジックは明確に責務分離可能です。特に `SearchInput` や `UserModal` は再利用前提で別コンポーネント化すべきです。

レビュー観点2:UI部品側の過剰な責務

逆にUI部品に以下のような実装がある場合も問題です。

UI部品がPageの依存を持っている例
export function UserModal({ userId }) {
  const user = useUser(userId);
  const { closeModal } = useDashboardContext();

  return (
    <Modal onClose={closeModal}>
      <p>{user.name}</p>
    </Modal>
  );
}
@Reviewer
`UserModal` が `useDashboardContext()` に依存しており、UI部品であるにもかかわらず特定のPageと結合しています。

こうした場合はPage側から props 経由で制御関数やデータを渡す構造に見直すのが基本です。

改善指針と提案

  • Page → データ取得・状態保持・ロジック判定
  • UI部品 → propsで制御、内部状態は最小限に
  • Page配下にのみ配置されるが再利用しない部品は features/〇〇/ に置くことで明確化
改善後の構造
export function DashboardPage() {
  const [search, setSearch] = useState('');
  const [isOpen, setIsOpen] = useState(false);
  const user = useUser();

  return (
    <>
      <Header />
      <SearchInput value={search} onChange={setSearch} />
      {isOpen && <UserModal user={user} onClose={() => setIsOpen(false)} />}
    </>
  );
}

終わりに

PageとUI部品の責務が曖昧になると、再利用性・保守性・可読性すべてが低下します。
レビューでは、構造の密結合が起きていないか、どのレイヤーがどの責務を持っているかに着目し、指摘と改善提案を行ってください。