Reactにおけるコンポジション
React では、props に ReactNode 型の値を渡すことで、コンポーネントを他のコンポーネントに埋め込むコンポジションが可能です。
function Parent(props) {
return <div>{props.children}</div>;
}
function Root(props) {
return (
<Parent>
<Button>Click</Button>
</Parent>
);
}
children 以外の props を利用するパターン
children 以外の props を使うことで、特定の要素を柔軟にカスタマイズできます。
function Parent(props) {
return (
<div>
{props.header}
{props.children}
</div>
);
}
function Root(props) {
return (
<Parent header={<div>Header</div>}>
<Button>Click</Button>
</Parent>
);
}
よく使われているパターン
Provider
Provider パターンでは、 children をそのまま受け取り、ラップする形で値を提供します。子要素の内容には関与しません。
import { createContext } from "react";
const Context = createContext("");
function Root(props) {
const value = getValue();
return (
<Context.Provider value={value}>
{props.children}
</Context.Provider>
);
}
children を使わないパターンとその問題点
children を使わずに Provider 内で直接子コンポーネントを描画するパターンも考えられますが、パフォーマンス面で問題があります。
import { createContext } from "react";
const Context = createContext("");
// childrenを渡す必要
function Root(props) {
const value = getValue();
return (
<Context.Provider value={value}>
<Child />
</Context.Provider>
);
}
この場合、 Root が再レンダリングされるたびに Child も再レンダリングされてしまいます。children を Provider に渡すことで、不要な再レンダリングを防ぐことができます。
コンポジションを使うことによるメリット
コンポーネントの再レンダリングの影響を受けにくい
通常、Reactでは親コンポーネントが再レンダリングされると、子コンポーネントも再レンダリングされます。しかし、コンポジションを利用して children として子コンポーネントを渡した場合、その子コンポーネントは親の再レンダリングの影響を受けません。
先ほどの Provider の例では、children として渡さないと Provider を含むコンポーネントが再レンダリングされるたびに Child も再レンダリングされてしまいます。children を適切に利用することで、この問題を回避できます。
React Server Components の活用
コンポジションは React Server Components (RSC) と相性が良いです。RSC では、親コンポーネントが Server Component か Client Component かによって、子コンポーネントの動作も変わります。1
しかし、コンポジションを利用すれば、Client Component 内に Server Component を組み込むことが可能です。
例えば、Next.js では Layout に Provider を配置することがあります。この場合、children を通じて Server Component をラップできます。
"use client"
function Provider(props) {
return (
<SomeProvider>
{props.children}
</SomeProvider>
);
}
// -----------------------
function Layout(props) {
return (
<Provider>
<ServerComponent />
</Provider>
);
}
この方法を使うことで、ページ全体を Client Component にすることなく、Provider の部分のみを Client Component にできます。
まとめ
コンポジションを活用することで、コンポーネントの子要素の詳細に依存せずに設計できるため、再レンダリングの影響を抑えやすくなります。
また、React Server Components (RSC) との組み合わせにより、Client Component の範囲を最小限に抑えつつ、柔軟なコンポーネント構成を実現できます。
適切なコンポジションの設計により、可読性・保守性・パフォーマンスの向上が期待できるため、積極的に取り入れていきましょう。
-
"use client" などのディレクティブを付与している場合は例外です。 ↩