【テクニカル・上級編】 コンポーネント合成(Composition)のパターン – React実践ガイド
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 (
);
};
// 使用側:疎結合により、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における合成とは、単なるコードの整理術ではない。それは、「時間経過とともに肥大化するアプリケーションの複雑性を、どうやって断片化し、維持し続けるか」という哲学だ。
コードを記述する際、常に自問自答してほしい。「このコンポーネントは、将来の仕様変更に対して柔軟か?」と。もし迷ったら、迷わず合成を選べ。継承の鎖を断ち切り、疎結合なパーツを組み合わせることで、初めて真に堅牢なアーキテクチャが姿を現すのだから。
さあ、エディタを開こう。継承という名の技術的負債を捨て、合成という名の自由を手に入れるために。
コメント