はじめに
業務の中でデザインパターンの一つである Compound Components というものを知ったので、どんなふうに使うのか?を記事にまとめてみました。
Compound Components とは
Compound Components は、全体を親コンポーネントで囲み、子コンポーネントをユーザーに指定させるパターンです。
データを Props 経由で渡し、一つのコンポーネントの中で全てを処理するのではなく、子コンポーネントとして渡すようにします。
Compound Components の利用例
Header / Body / Footer を共通レイアウト化して使えるようにしたいが、ページによって少しカスタマイズしたい、といったケースで考えてみます。
Compound Components を使わない実装例
「Footer は全ページ共通だが、Header は条件により出し分けたい」とします。
type Props = {
globalMenuList?: GlobalMenu[]
pageTitle: string
children: ReactNode
}
export const CommonLayout: React.FC<Props> = ({ globalMenuList, pageTitle, children }) => (
<div>
{globalMenuList ? (
<Header pageTitle={pageTitle}>
<GlobalMenu globalMenuList={globalMenuList} />
</Header>
) : (
<Header pageTitle={pageTitle} />
)}
{children}
<Footer />
</div>
)
↓使用イメージ
<CommonLayout globalMenuList={globalMenuList}>
<div>
body の中身
</div>
</CommonLayout>
JSX のなかで条件によって表示を出し分けたいときは条件付きレンダーを使うことが多いと思います。
上記のコード例は非常にシンプルなのでそこまで複雑には見えないですが、実務だとコード量がさらに大きく、条件つきレンダーを使用する箇所も増えてくるため、見づらく感じることがあります。
また、Header まで全て CommonLayout
のなかで表現しようとすると、レイアウトの共通化をしたい目的で作っているのに、 CommonLayout
が知っていることが増えてしまっています。
Compound Components を用いた実装例
CommonLayout
から Header 部分を CommonLayoutHeader
に切り出してみます。
type CommonLayoutHeaderProps = {
children?: GlobalMenu[]
pageTitle: string
}
type CommonLayoutProps = {
children: ReactNode
}
export const CommonLayoutHeader: React.FC<CommonLayoutHeaderProps> = ({ children, pageTitle }) => (
<Header pageTitle={pageTitle}>
{children}
</Header>
)
export const CommonLayout: React.FC<CommonLayoutProps> = ({ children }) => (
<div>
{children}
<Footer />
</div>
)
↓使用イメージ
// グローバルメニューを出したいページでは children として GlobalMenu を渡す
<CommonLayout>
<CommonLayoutHeader pageTitle={pageTitle}>
<GlobalMenu globalMenuList={globalMenuList} />
</CommonLayoutHeader>
<div>
body の中身
</div>
</CommonLayout>
// グローバルメニューを出さないページでは children を渡さないようにする
<CommonLayout>
<CommonLayoutHeader pageTitle={pageTitle} />
<div>
body の中身
</div>
</CommonLayout>
使う側から GlobalMenu を渡すことができるようになり、 適切に CommonLayout との責務の分割ができています!
さいごに
なんでも props として渡すと渡すものがどんどん膨れ上がったり、props が多いと「これを渡すとどうなるのか?」がどんどん複雑になっていってしまいます。
こんなときに Compound Components を取り入れ、データを子コンポーネントへ渡すようにすることで直感的にも使いやすいし、責務の分割も適切にできるようになる、と感じました。
適材適所で使い分けができるようになっていきたいです。
参考