はじめに

Reactコンポーネントは表示の単位として構築されることが多いですが、実際にはUI描画とロジック処理が混在した構造になりやすい傾向があります。
これは「ちょっとした処理だから」とその場に書いてしまう習慣から発生しがちですが、蓄積すると読みづらく、再利用しづらく、テストも難しい構造になります。

この記事では、UI処理にロジックが混ざっている構造の問題点をレビュー視点で分析し、どのように設計を分離すべきかを整理していきます。

UIにロジックが入り込むパターンとは

以下のような構造は、UIコンポーネントにロジックが混在していると判断できる典型例です。

  • 描画中にフィルターやマッピングなどの変換処理をその場で行っている
  • 条件式が複雑で、UIとロジックが混在している
  • useEffectなどの副作用が描画と一緒に存在している
  • データ構造の正規化・加工をrender関数内で処理している

レビューでは、これらが「一見単純に見えるが蓄積すると深刻になる」という視点で見ていく必要があります。

混在構造の具体例

UIにロジックが混ざっている構造
export const ProductTable = ({ products }) => {
  const visibleItems = products
    .filter(p => p.isActive && p.stock > 0)
    .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
  return (
    <table>
      <tbody>
        {visibleItems.map(item => (
          <tr key={item.id}>
            <td>{item.name}</td>
            <td>{item.stock}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
};
@Reviewer
フィルタ・ソート処理がUI層の内部に混在しています。見た目に集中すべき構造でデータ処理が入り込んでおり、責務の分離が曖昧です。データ処理部分は呼び出し側または専用のカスタムフックに分離してください。

この構造では、表示用コンポーネントが「表示対象の判定」というロジック責務を抱えてしまっています。

ロジックを切り出す改善例

データ処理を親で責任分離した例
const ProductTable = ({ items }) => (
  <table>
    <tbody>
      {items.map(item => (
        <tr key={item.id}>
          <td>{item.name}</td>
          <td>{item.stock}</td>
        </tr>
      ))}
    </tbody>
  </table>
);
const ProductTableContainer = ({ products }) => {
  const visibleItems = useMemo(() => {
    return products
      .filter(p => p.isActive && p.stock > 0)
      .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
  }, [products]);
  return <ProductTable items={visibleItems} />;
};

ここでは、フィルタ・ソートといった処理責務はProductTableContainerに集約され、ProductTableは完全に表示専用になっています。

複雑な条件式を含むUI構造

判定ロジックが表示に埋め込まれている例
const AlertBanner = ({ user }) => {
  if (
    !user ||
    (user.isLoggedIn && !user.hasAcceptedTerms && user.age > 18)
  ) {
    return <div className="alert">利用規約に同意してください</div>;
  }
  return null;
};
@Reviewer
ロジック式が長く、表示と条件判断が一体化しています。`shouldShowAlert(user)`のように切り出して表現の明確さを保ってください。

条件分岐が複雑な場合、表示意図が見えづらくなり、修正時の誤解を招きやすい構造になります。

表示層から責務を切り出す手法

責務を分離するためには、以下のような手法を適用できます。

  • カスタムフックを作成し、ロジックだけを独立させる
  • 親コンポーネントでロジックを事前処理してから渡す
  • 条件式は関数に抽出して命名により意図を明確にする
  • 入力やロジック部分をContextに任せ、UI層から見えなくする
条件式を関数に切り出す例
const shouldShowAlert = (user) =>
  user?.isLoggedIn && !user.hasAcceptedTerms && user.age > 18;
const AlertBanner = ({ user }) => {
  return shouldShowAlert(user) ? (
    <div className="alert">利用規約に同意してください</div>
  ) : null;
};

これにより、UI部分では「このコンポーネントは何を表示しているか」に集中できる構造になります。

混在構造と分離構造の比較

UML Diagram

レビュー観点まとめ

UIとロジックが混在していないかを確認するために、以下の観点が有効です。

  • render関数内にフィルタ、ソート、計算処理が存在していないか
  • 条件式が長大になっており、表示意図が読み取りづらくなっていないか
  • 状態やpropsを加工する責務がUI層に入り込んでいないか
  • 表示部が「いつ・なぜ表示するか」を説明できるか(命名によって)
  • 表示用コンポーネントがテスト可能な構造になっているか

レビューアーとしては、「ちょっとした処理」でもそれがUI構造の責務と整合しているかを丁寧に見ていく必要があります。

UI層は「表示だけに責任を持つ構造」にすることが、Reactコンポーネントの健全性を保つ設計方針のひとつです。