はじめに

Reactコンポーネントをレビューしているとき、こう感じたことはないでしょうか:

状態もAPIもロジックもUIも、全部このコンポーネントにある…何がどこで何をしてるのか分からない。

これはまさに「レイヤーの責務が混在している構造」です。

Reactは柔軟にコードが書ける分、適切な抽象レイヤーを明示しないと、構造がどんどん崩れていきます。
この記事では、レビューアーとしてその“混在構造”をどう見抜き、どう分離・再構成を提案していくかを整理します。

「レイヤーが混在している」とはどういうことか

混在とは

状態管理(useState/useReducer)、非同期処理(fetch/dispatch)、UI描画(JSX)、副作用(useEffect)など本来レイヤーを分けて構造化すべき処理が、単一のコンポーネントに全て含まれている状態を指します。

このような混在構造は次のような問題を引き起こします:

  • 可読性が低下し、意図を読み解くのに時間がかかる
  • テストがしづらくなる(UIとロジックが密結合)
  • 責務の明確な変更が困難になる(UI変更とロジック変更が干渉)

混在構造の実例

責務が混ざっている構造
export function Dashboard() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch('/api/data')
      .then(res => res.json())
      .then(setData)
      .finally(() => setLoading(false));
  }, []);

  return (
    <div>
      {loading ? <Spinner /> : <Chart data={data} />}
    </div>
  );
@Reviewer
状態管理・非同期処理・UI描画が全てこのコンポーネントに集中しています。
レイヤーごとに分割することで、責務が明確になり保守性が向上します。
}

見た目がシンプルでも、内部的には複数レイヤーが交差しており、変更や拡張が困難な構造になっています。

レビュー時のチェックポイント

チェック項目 内容
状態・副作用・UIがすべて同じ関数内にあるか? useStateuseEffect・JSXが同列で記述されている
API呼び出しと表示ロジックが同居していないか? fetchreturnが隣接している
イベントハンドラがUI層にベタ書きされていないか? onClick={async () => ...} など
コンテナ層とプレゼンテーション層が分かれているか? Container + Pure Component に分離できる余地がある

分離パターンの提案

パターン1:Custom Hookでロジックを抽出

useDashboardData.ts
export function useDashboardData() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);
    fetch('/api/data')
      .then(res => res.json())
      .then(setData)
      .finally(() => setLoading(false));
  }, []);

  return { data, loading };
}
Dashboard.tsx
const { data, loading } = useDashboardData();
return loading ? <Spinner /> : <Chart data={data} />;

UI層と状態ロジックが分離され、役割が明確になります。

パターン2:Container / Componentに分割

DashboardContainer.tsx
export function DashboardContainer() {
  const { data, loading } = useDashboardData();
  return <DashboardView data={data} loading={loading} />;
}
DashboardView.tsx
export function DashboardView({ data, loading }) {
  return loading ? <Spinner /> : <Chart data={data} />;
}
  • Container:状態保持・副作用
  • View:UI専用

この分割により、UIの変更とロジックの変更が独立して行えるようになります。

構造の違いを可視化

UML Diagram
UML Diagram

分離後の構造は各責務が独立しており、保守性・再利用性に優れます。

コメントテンプレート(レビュー用)

責務混在の指摘
@Reviewer: このコンポーネントは状態・副作用・UI描画のすべてを含んでおり、構造的に責務が混在しています。Custom Hookへの分離やContainer構造の導入により、各責務を明確に分離することが望ましいです。
改善提案例
- export function Dashboard() {
-   const [data, setData] = ...
-   useEffect(() => { ... })
-   return <Chart />
+ const { data, loading } = useDashboardData();
+ return <DashboardView data={data} loading={loading} />

まとめ

同じレイヤーに異なる責務が混在している構造は、初期実装時は便利でも、後から変更しようとしたときに構造の限界が露呈します。

レビューアーは、見た目の短さや動作の正しさではなく、構造が拡張や修正に耐えられるかを基準にチェックすることが重要です。

責務の明確なレイヤー分離こそが、長期的に読みやすく保守しやすいReact構造への第一歩です。