「継承」を捨て、「合成」で勝つ。Reactコンポーネント設計の極意
やあ。現場でコードを書いていて、こんな経験はないかな?
「汎用的な基底コンポーネントを作ったけど、例外的なパターンが増えるたびに `if-else` や膨大なPropsが増殖して、最終的に誰も触りたくないモンスターコードが完成した」
もし心当たりがあるなら、それはReactの設計思想である「合成(Composition)」の力をまだフルに引き出せていない証拠だ。オブジェクト指向の「継承」に慣れた頭を一度リセットしよう。Reactにおいて、再利用性は「クラスの継承」ではなく「コンポーネントの合成」から生まれるんだ。
—
なぜ「合成」が最強の選択肢なのか
Reactの仮想DOMは、コンポーネントツリーを「関数がネストされたツリー構造」として捉えている。ブラウザ上のDOMは単なる木構造だが、Reactはその一つひとつの枝や葉を、疎結合なパーツとして組み立てることを推奨している。
継承(Inheritance)を使ってUIを構築しようとすると、親の振る舞いが子に強制される「密結合」に陥る。しかし、合成は違う。「箱の中に何を入れるか」を呼び出し側で決める。この「制御の反転」こそが、ReactをReactたらしめている真髄だ。
—
現場で即戦力になる「スロットパターン」
実務で最も直感的に使えるのが、`children` を活用したスロットパターンだ。複雑なUIを、外枠(レイアウト)と中身(コンテンツ)に分離するだけで、コードの見通しは劇的に良くなる。
悪い例:Propsで無理やり制御するコンポーネント
// どこが悪いか:タイトル、ボタン、アイコン…とPropsが増え続け、管理不能になる
const Card = ({ title, showButton, buttonText, onClick }) => (
{title}
{showButton && }
);
良い例:合成による柔軟な設計
// Cardは「枠」に徹する。中身は呼び出し側で自由に注入する
const Card = ({ children }) => {
return
;
};
// これにより、Cardの内部構造を知らなくても、好きなUIを組み立てられる
const UserProfile = () => (
ユーザー名
);
—
さらに深く:名前付きスロットで責務を分離する
`children` だけでは足りない、より複雑なUIの場合は、コンポーネントをPropsとして渡す「名前付きスロット」という手法をとる。これができると、チーム開発の効率は一気に上がる。
// 複数のエリアを持つレイアウトコンポーネント
const PageLayout = ({ header, sidebar, main }) => {
return (
);
};
// 呼び出し側(これが驚くほど綺麗になる)
const Dashboard = () => (
sidebar={
main={
/>
);
なぜこれが「実務レベル」で重要なのか?
1. テストの簡素化: `PageLayout` 自体のテストは「各スロットが正しく配置されるか」を確認するだけでいい。中身のロジックと分離されているため、バグの切り分けが非常に容易になる。
2. Prop Drillingの回避: 深い階層にPropsをリレーさせる必要がない。必要なコンポーネントをその場で注入するからだ。
3. カプセル化: `PageLayout` は中のコンポーネントが何をしているかを知らなくていい。ただ「そこにあるものを表示する」という責務に専念できる。
—
現場のシニアからのアドバイス
「何でもかんでもコンポーネントに分割すればいい」というのは罠だ。
「変更の理由が同じものをまとめ、変更の理由が異なるものを分ける」。これがコンポーネント設計の唯一の黄金律だ。
合成を意識し始めると、自然とコンポーネントは「純粋」になっていく。ブラウザが仮想DOMをレンダリングする際も、分割されたコンポーネントの方が差分検出(Reconciliation)の対象を絞り込みやすく、パフォーマンス上のメリットも享受できる。
まずは、今書いている巨大なコンポーネントのPropsを見てほしい。「これって、`children` で渡せるんじゃないか?」と自問自答する。その一歩から、君のコードは劇的に美しく、そして堅牢に変わっていくはずだ。
迷ったら、シンプルに組み立てろ。Reactは、君が想像する以上に君の自由を尊重してくれるフレームワークなんだから。

コメント