はじめに

Reactでは、親から子へとデータを渡す手段としてpropsが多用されます。
ただし開発が進むと、propsの構造が肥大化し、再利用や可読性に悪影響を及ぼすことがあります。

また、ReduxやRecoil、Zustandなどの状態管理ツールが導入されているプロジェクトにおいても、「全てをグローバルステートで管理せず、どこまでをpropsで渡すか」は設計判断の焦点となります。

この記事では、propsに依存しすぎたコンポーネント構造がなぜ設計の歪みを生むのかをレビュー観点から整理し、再設計のポイントを提示します。

props依存が設計上の問題となる背景

propsは便利な仕組みですが、以下のような状態に陥ると構造的な問題になります。

  • 渡すデータが10個以上ある(量の問題)
  • 同じpropsを3階層以上で受け渡している(深さの問題)
  • propsのネーミングがコンテキスト依存で読みにくい(意味の問題)
  • 子コンポーネントが渡されたpropsの構造を知りすぎている(密結合の問題)

こうした状態は、「一見動いているが再利用も保守もしにくい」コードの典型です。

propsが多すぎる構造の例

まずはpropsの数が多すぎる構造の例を見てみましょう。

props依存が過剰な構造
export const UserProfile = ({
  userId,
  userName,
  avatarUrl,
  isVerified,
  email,
  phone,
  createdAt,
  updatedAt,
  onEdit,
  onDelete
}) => {
  return (
    <div>
      <img src={avatarUrl} alt={userName} />
      <h2>{userName}</h2>
      {isVerified && <span></span>}
      <p>{email}</p>
      <p>{phone}</p>
      <button onClick={() => onEdit(userId)}>編集</button>
      <button onClick={() => onDelete(userId)}>削除</button>
    </div>
  );
};
@Reviewer
単一のユーザー情報を扱っているにもかかわらず、propsの分解が細かすぎて責務の境界が曖昧になっています。propsを1つのuserオブジェクトに集約するか、構造化して意味のある塊として扱うよう見直しましょう。

このように、propsがフラットに並びすぎている場合、構造上の塊が見えなくなります。

propsのネストが深い構造の例

次に、propsの構造が深くなりすぎているケースです。

ネストが深すぎるprops参照
export const PaymentSummary = ({ user, payment }) => {
  return (
    <div>
      <p>{user.profile.name}さんの購入情報</p>
      <p>商品: {payment.detail.product.name}</p>
      <p>合計: {payment.detail.amount.total}</p>
    </div>
  );
};
@Reviewer
props内でuserやpaymentの構造に深く依存しており、呼び出し側の構造が変わると壊れやすくなっています。UIに必要な情報だけを渡すか、プレゼンテーショナル化を検討してください。

表示側で内部構造を掘りすぎると、親側の責務を引き受けすぎることになります。

コンテキストが不明瞭なprops構造の例

propsの中でも「何のための値なのか」が分かりづらい構造も、レビュー対象になります。

意味があいまいなprops名
export const ActionButton = ({ value, mode, type, onChange }) => {
  return (
    <button onClick={() => onChange(value)}>{type}</button>
  );
};
@Reviewer
props名にcontextがなく、呼び出し側の目的が不明瞭です。特にvalueやtypeなどの汎用名は、責務が読み取れません。呼び出し目的ごとに具体的な構造に分割しましょう。

props名だけでは意図が分からず、コンポーネントの責務が不透明になります。

props drilling(深い受け渡し)の検出観点

次に、props drillingの構造を見ていきます。これはpropsを多段に渡していく構造で、コンポーネント数が増えるほど問題が顕著になります。

props drillingの例
export const App = () => {
  const theme = 'dark';
  return <Layout theme={theme} />;
};
const Layout = ({ theme }) => <Header theme={theme} />;
const Header = ({ theme }) => <UserMenu theme={theme} />;
const UserMenu = ({ theme }) => <button className={`btn ${theme}`}>設定</button>;
@Reviewer
themeが4階層にわたって受け渡されています。コンテキストの使用を検討するか、UserMenu側にローカルな状態として吸収可能かレビューすべきです。

props drillingの検出は、レビューアーが構造全体をトレースできるかどうかにかかっています。

構造改善の観点

propsが多い、深い、意味が薄いといった状況への対応として、以下の方針が有効です。

  • 意味のある構造体にまとめる(例:user, paymentなど)
  • 不要なpropsを渡さず、必要な粒度にして分割する
  • 表示に必要な最低限のデータのみを子に渡す
  • 子で深掘りしない構造を保つ(非依存なプレゼンテーション層の設計)
改善された構造
export const UserProfile = ({ user, onEdit, onDelete }) => {
  return (
    <div>
      <img src={user.avatarUrl} alt={user.name} />
      <h2>{user.name}</h2>
      {user.isVerified && <span></span>}
      <p>{user.email}</p>
      <p>{user.phone}</p>
      <button onClick={() => onEdit(user.id)}>編集</button>
      <button onClick={() => onDelete(user.id)}>削除</button>
    </div>
  );
};

このようにpropsの意味と責務を整理するだけで、コンポーネントの見通しは大きく改善されます。

構造図:props密結合の構成と改善方向

UML Diagram

状態管理を使いたくない場合のprops整理案

propsの数が多くなってしまうのは、責務が明確になっていない場合だけでなく、実際に渡すべき情報が多いケースもあります。
ただし、状態管理ツール(Redux, Recoil, etc)を使わずにこれに対処したい場合、レビューアーとしては以下のような選択肢を評価・提案することになります。

1. 意味のある構造体でまとめる

propsをフラットに渡すのではなく、関連する情報をオブジェクトや型として意味を持たせてまとめる方法です。

ユーザー関連情報を1つの構造に集約
export const UserCard = ({ user, actions }) => (
  <div>
    <p>{user.name}</p>
    <button onClick={() => actions.edit(user.id)}>編集</button>
  </div>
);

この構造なら、呼び出し元でも{ user, actions }を準備する責務が明確になります。

2. コンポーネントを再分割する

一つのコンポーネントに責務が集中してpropsが多くなっている場合、UI単位で分割することで1ファイルあたりのpropsを減らすことができます。

分割前
<ProfileCard
  user={user}
  stats={stats}
  settings={settings}
  onUpdate={onUpdate}
  onDelete={onDelete}
/>
分割後
<ProfileSummary user={user} stats={stats} />
<ProfileActions onUpdate={onUpdate} onDelete={onDelete} />

この分割により、props数そのものは減らなくても各コンポーネントに渡す数は最小化され、責務が明確になります。

3. children + カスタムレンダラーで責務を切り出す

描画内容を呼び出し側に移譲する構造も、props数の肥大化を避けたいときの選択肢です。

カスタムUIをchildrenに逃す構造
export const ListContainer = ({ items, renderItem }) => (
  <ul>
    {items.map(item => (
      <li key={item.id}>{renderItem(item)}</li>
    ))}
  </ul>
);
// 呼び出し側
<ListContainer
  items={users}
  renderItem={user => <UserItem user={user} />}
 />

childrenやrender propsで描画責任を分散することで、親側に集中していたprops設計を緩和できます。

4. 明示的に「設計の限界」であることを共有する

どうしてもpropsが多くなる場面では、無理に数を減らすのではなく、「これ以上は再設計が必要」というラインをチーム内で共有することも現実的です。

開発ドキュメントへの記載例
@Reviewer: このコンポーネントのprops数が多いのはUI要件に由来する構造上の事情です。再分割や抽象化のコストと照らし、現時点ではこの構成を妥当と判断しています。

レビューアーは、「propsが多い=設計ミス」と決めつけるのではなく、構造と背景に対して柔軟な読み取りを行うことが求められます。

レビュー観点まとめ

propsに依存しすぎる構造は、設計判断の曖昧さがそのまま現れます。レビュー時には以下の点を確認しましょう。

  • propsの数は過剰でないか(6~8個を超える場合は構造を見直す)
  • propsに意味のある命名と構造があるか
  • 子コンポーネントが親のデータ構造に依存しすぎていないか
  • props drillingが複数階層続いていないか
  • 状態や構造をContextや別レイヤーに切り出す余地があるか

また、状態管理ツールの導入によってpropsを渡さずとも値を取得できる構造が増えてきていますが、その設計は「責務の所在を不明瞭にする」リスクと背中合わせです。
グローバルステートを使うかpropsを渡すかは、レビューのたびに構造上の意図を読み取り、再検討すべき対象です。

構造の見通しを良くするためにも、propsの持ち方はレビューにおける最重要観点の一つです。