はじめに

Reactでは「小さな部品に分けて組み立てる」ことが基本思想のひとつです。
しかしその過程で、コンポーネント同士が過剰に依存し合う構造になってしまうケースがあります。

  • 子が親の状態や副作用に依存している
  • AコンポーネントがBコンポーネントの内部実装を前提として動作している
  • 組み合わせ順やラップ構造に強く依存している

この記事では、構造的な結合度が高すぎる状態をどうレビューで見抜くか、どう改善すべきかを整理していきます。

構造的依存が問題になるケース

以下のような依存関係は、レビューで注視すべき対象です。

  • 子コンポーネントが親のuseEffectの結果を前提にして動作している
  • 子コンポーネントがpropsとして渡された値の特定の構造や順番に依存している
  • 同一レイヤーにあるべきコンポーネントが、内部ロジックで他のコンポーネントに処理を委ねている
  • 表示責務だけのコンポーネントが、別の副作用系コンポーネントに依存して再描画のタイミングが崩れている

こうしたケースは「壊れやすい構造」の典型です。

依存が強すぎる構造の例

子コンポーネントが親構造に依存
export const Parent = () => {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetch('/api/me')
      .then(res => res.json())
      .then(setUser);
  }, []);
  return <UserGreeting name={user?.name} />;
};
const UserGreeting = ({ name }) => {
  return <div>{name ? `こんにちは、${name}さん` : '読み込み中...'}</div>;
};
@Reviewer
UserGreetingがnullやundefinedを受けることを前提にしており、親の副作用と密接に結合しています。nameの責務が曖昧で、ロジック上の責任分離ができていません。

このように、親の状態に強く依存した子コンポーネントは、再利用性が極端に低くなり、構造の変更にも弱くなります。

props構造への依存

propsの構造が過剰に前提化されているパターンも、レビューではよく見かけます。

構造依存が強い例
const UserDetail = ({ user }) => {
  return (
    <div>
      <p>{user.profile.name}</p>
      <p>{user.account.status}</p>
    </div>
  );
};
@Reviewer
子コンポーネントが親のデータ構造を深く知っている状態です。これは構造的な密結合であり、親のリファクタリングに弱くなります。

このような設計は、表示ロジックが親の内部構造とロジックの変更に引きずられやすくなるため、propsの受け渡し粒度を見直す必要があります。

コンポーネントの順序依存

あるコンポーネントが特定の構造内でのみ正しく動作するという設計も、依存度が高い設計の一例です。

ラップ構造に依存した設計
export const App = () => (
  <FormProvider>
    <SubmitButton />
  </FormProvider>
);
const SubmitButton = () => {
  const form = useFormContext();
  return <button onClick={form.submit}>送信</button>;
};
@Reviewer
SubmitButtonはFormProvider内でしか動作しない構造です。呼び出し順やラップ構造に強く依存しているため、単独利用や再構成時に壊れやすくなります。

このような依存関係は「ラップし忘れると壊れる」という事故を招くため、レビュー時には責務の境界と前提の明示を確認します。

独立性の高い構造の設計例

依存を減らし、再利用可能な構造を実現するには、以下のように設計を見直します。

再利用を意識した構造
const UserGreeting = ({ name }) => {
  return <div>{name ? `こんにちは、${name}さん` : 'ユーザー名未設定'}</div>;
};
const Parent = () => {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetch('/api/me')
      .then(res => res.json())
      .then(setUser);
  }, []);
  return user ? <UserGreeting name={user.name} /> : <p>読み込み中...</p>;
};

ここでは、UserGreetingは描画にのみ責務を限定し、nullや非同期に関する処理を持たないことで、構造の独立性を保っています。

依存構造と分離構造の比較

UML Diagram

状態管理やカスタムHookに逃す選択肢

コンポーネント間で状態やロジックを共有する必要がある場合でも、propsと構造に直接依存するのではなく、カスタムHookやContextを利用することで依存度を緩和する選択肢があります。

カスタムHookによる構造の抽象化
const useUserName = () => {
  const { user } = useContext(AuthContext);
  return user?.name || '';
};
const Header = () => {
  const name = useUserName();
  return <div>{name}</div>;
};

このように抽象化することで、表示側が構造に依存することなく必要な情報のみを取得可能になります。

レビュー観点まとめ

レビュー時には以下のような点に注目して、構造的な依存関係を見極めていくことが重要です。

  • 子コンポーネントが親の副作用や非同期処理に依存していないか
  • 子がpropsのネスト構造や形式に強く依存していないか
  • 順序やラップ構造の前提に縛られていないか
  • 親の状態変更がそのまま子の再描画や挙動に影響していないか
  • コンポーネントの独立性を維持できるよう、責務の境界が明確か

結合度の高さは、構造が壊れやすい設計の兆候です。
レビューでは単なるpropsの受け渡しだけでなく、構造の自由度と独立性の確保という観点で見抜いていく必要があります。