【テクニカル・上級編】 コンポーネント合成(Composition)のパターン – React実践ガイド

継承という幻想を捨てよ:Reactにおけるコンポーネント合成の極意

Reactの学習初期、多くのエンジニアが「継承(Inheritance)はどこだ?」と探す。しかし、我々熟練のアーキテクトが口を揃えて言うのは、「Reactにおいて継承はアンチパターンである」という冷徹な事実だ。

コンポーネントのロジックを再利用しようとして、基底クラスを作り、階層を深くする……そんなJavaやC#の悪夢をReactに持ち込んではいけない。Reactの真髄は「合成(Composition)」にある。今日は、単なる`children`の渡し方を超えた、堅牢で高パフォーマンスなコンポーネント設計の深淵に踏み込もう。

—

1. なぜ「合成」が「継承」に勝るのか

継承は、親が子を縛る密結合の構造を生む。一方、合成は「何をどう組み合わせるか」という宣言的な関係性だ。

特に、コンポーネントの責務が肥大化する際、継承では「Propsのバケツリレー」という地獄が待ち受けている。合成を用いれば、コンポーネントは「自分が何を受け取るか」を知る必要がなくなり、単なるUIの断片としての純粋性を保てる。これはレンダリング最適化の観点でも極めて重要だ。

—

2. スロットパターンによる疎結合なアーキテクチャ

単に `children` を渡すだけでは、複雑なUIを構築するには力不足だ。そこで推奨するのが「名前付きスロット」パターンである。これにより、親コンポーネントの内部構造を隠蔽しつつ、子コンポーネントの配置を柔軟に制御できる。

import React, { ReactNode } from ‘react’;

// 構造を定義するコンポーネント。内部の実装を外部から制御可能にする
const Card = ({
header,
children,
footer
}: {
header: ReactNode;
children: ReactNode;
footer: ReactNode;
}) => {
return (

{header}

{children}

{footer}

);
};

// 使用側:疎結合により、Cardの内部構造を知らずに組み立てられる
const App = () => (

タイトル

}
footer={}
>

メインコンテンツ。ここは自由に拡張可能。


);

なぜこれが効率的なのか

この手法の最大の利点は、`Card`コンポーネントの再レンダリングを制御しやすい点にある。もし`header`や`footer`が巨大なコンポーネントであっても、それらが`App`側で定義されていれば、`Card`のPropsとして渡されるのは既に評価済みのJSX要素だ。ReactのReconciliationプロセスにおいて、これらは「安定した参照」として扱われやすく、不要な再レンダリングの連鎖を防ぐ防波堤となる。

—

3. レンダリング負荷とパフォーマンスの最適化

上級エンジニアであれば、コンポーネントの「再レンダリングの伝搬」には敏感であるべきだ。合成において特に陥りやすい罠が、「Propsとして関数やオブジェクトをインラインで生成し続けること」である。

避けるべき実装

// 毎レンダリングごとに新しいオブジェクトが生成され、メモ化が無効化される

改善案:レンダープロップとメモ化の共存

もしコンポーネント間で動的な状態共有が必要なら、`children`を関数にする「レンダープロップ」を活用しつつ、`useMemo`で値を固定する。

const DataProvider = ({ children }: { children: (data: any) => ReactNode }) => {
const [data] = React.useState({ id: 1 });
// 値をメモ化し、子コンポーネントの不必要なレンダリングを防ぐ
const memoizedData = React.useMemo(() => data, [data]);
return <>{children(memoizedData)};
};

—

4. コンポーネント合成における非同期の競合回避

複数のスロットを合成する際、各スロットが非同期データに依存していると、レンダリングタイミングのズレがUIのガタつき(Layout Shift)を生む。

これを防ぐための高度なテクニックは、「Suspense Boundaryを合成のレイヤーに埋め込むこと」だ。コンポーネントの合成自体を「関心の分離」と捉え、データ取得層と表示層を明確に分ける。

1. データ取得ロジックを分離: `useQuery` や `useFetch` をカスタムフック化する。
2. 境界を設計: 合成されたコンポーネントの内部で `Suspense` を配置し、読み込み状態の責任を該当コンポーネント内に閉じる。

これにより、親コンポーネントは「子の中身が読み込み中か否か」を意識することなく、単に合成を行うだけで済む。これが、大規模開発における唯一無二の正解だ。

—

最後に:職人としての心得

Reactにおける合成とは、単なるコードの整理術ではない。それは、「時間経過とともに肥大化するアプリケーションの複雑性を、どうやって断片化し、維持し続けるか」という哲学だ。

コードを記述する際、常に自問自答してほしい。「このコンポーネントは、将来の仕様変更に対して柔軟か?」と。もし迷ったら、迷わず合成を選べ。継承の鎖を断ち切り、疎結合なパーツを組み合わせることで、初めて真に堅牢なアーキテクチャが姿を現すのだから。

さあ、エディタを開こう。継承という名の技術的負債を捨て、合成という名の自由を手に入れるために。

コメント

タイトルとURLをコピーしました