目次
おさらい: 子要素をコンポジションで出力する方法
コンポジションとはコンポーネント間のコードを再利用するためのReactの機能です。
コンポジションではchildren
という特別なPropsを利用して子コンポーネントを出力します。
App.tsx
import { FC } from "react";
import { FancyBorder } from "./FancyBorder";
const App: FC = () => {
return <FancyBorder>App</FancyBorder>;
};
export default App;
FancyBorder.tsx
import { FC, ReactNode } from "react";
import styles from "./FancyBorder.module.css";
type Props = {
children: ReactNode;
};
export const FancyBorder: FC<Props> = ({ children }) => {
return <div className={styles.fancyBorder}>{children}</div>;
};
FancyBorder.module.css
.fancyBorder {
padding: 10px 10px;
border: 10px solid;
border-color: blue;
}
複数の子要素をコンポジションで出力する方法
children
の代わりに独自のPropsを作成することで複数の子要素を出力できます。
今回は2つ方法を紹介します。
方法1: Reactの要素(ReactNode)をPropsで渡す
以下はheader
とcontents
というPropsにReactの要素(ReactNode)を渡し、コンポジションで表示する例です。
App.tsx
import { FC } from "react";
import { FancyHeader } from "./FancyHeader";
import { FancyContents } from "./FancyContents";
import { Template } from "./Template";
const App: FC = () => {
return <Template header={<FancyHeader />} contents={<FancyContents />} />;
};
export default App;
Tamplate.tsx
import { FC, ReactNode } from "react";
type Props = {
header: ReactNode;
contents: ReactNode;
};
export const Template: FC<Props> = ({ header, contents }) => {
return (
<>
{header}
{contents}
</>
);
};
FancyHeader.tsx
import { FC } from "react";
import styles from "./FancyHeader.module.css";
export const FancyHeader: FC = () => {
return <h1 className={styles.fancyHeader}>FancyHeader</h1>;
};
FancyHeader.module.css
.fancyHeader {
padding: 10px 10px;
border: 10px solid;
border-color: red;
}
FancyContents.tsx
import { FC } from "react";
import styles from "./FancyContents.module.css";
export const FancyContents: FC = () => {
return <div className={styles.fancyContents}>FancyContents</div>;
};
FancyContents.module.css
.fancyContents {
padding: 10px 10px;
border: 10px solid;
border-color: blue;
}
方法2: 関数コンポーネント(React.FC)をPropsで渡す
以下はHeader
とContents
というPropsに関数コンポーネント(React.FC)を渡し、コンポジションで表示する例です。
App.tsx
import { FC } from "react";
import { FancyHeader } from "./FancyHeader";
import { FancyContents } from "./FancyContents";
import { Template } from "./Template";
const App: FC = () => {
return <Template Header={FancyHeader} Contents={FancyContents} />;
};
export default App;
Template.tsx
import { FC } from "react";
type Props = {
Header: FC;
Contents: FC;
};
export const Template: FC<Props> = ({ Header, Contents }) => {
return (
<>
<Header />
<Contents />
</>
);
};
参考: 型エイリアスを追加する
コンポジションで出力するコンポーネントに関数型の型エイリアスを追加すると以下のようになります。
Template.tsx
import { FC } from "react";
type Props = {
Header: FC;
Contents: FC;
};
+ export type TemplateHeaderComponent = () => FC;
+ export type TemplateContentsComponent = () => FC;
export const Template: FC<Props> = ({ Header, Contents }) => {
return (
<>
<Header />
<Contents />
</>
);
};
FancyHeader.tsx
- import { FC } from "react";
+ import { TemplateHeaderComponent } from "./Template";
import styles from "./FancyHeader.module.css";
- export const FancyHeader: FC = () => {
+ export const FancyHeader: TemplateHeaderComponent = () => () => {
return <h1 className={styles.fancyHeader}>FancyHeader</h1>;
};
FancyContents.tsx
- import { FC } from "react";
+ import { TemplateContentsComponent } from "./Template";
- export const FancyContents: FC = () => {
+ export const FancyContents: TemplateContentsComponent = () => () => {
return <div className={styles.fancyContents}>FancyContents</div>;
};
App.tsx
import { FC } from "react";
import { FancyHeader } from "./FancyHeader";
import { FancyContents } from "./FancyContents";
import { Template } from "./Template";
const App: FC = () => {
- return <Template Header={FancyHeader} Contents={FancyContents} />;
+ return <Template Header={FancyHeader()} Contents={FancyContents()} />;
};
export default App;
複数の子要素をコンポジションで出力する具体例
複数の子要素をコンポジションで出力する具体例として「ツールチップ」を紹介します。
「マウスオーバーの対象要素」と「マウスオーバーによって表示される要素」の2つをコンポジションで管理します。
App.tsx
import { FC } from "react";
import { Tooltip } from "./shared/Tooltip";
import { Header } from "./Header";
import { HeaderDetail } from "./HeaderDetail";
const App: FC = () => {
return (
<Tooltip
Target={Header({
title: "Title",
})}
Body={HeaderDetail({
description: "Description",
})}
/>
);
};
export default App;
Tooltip.tsx
import { FC, useState } from "react";
import styles from "./Tooltip.module.css";
type Props = {
Target: FC<TargetProps>;
Body: FC;
};
type TargetProps = {
hovered: boolean;
};
export type TooltipTargetFC<T> = (props: T) => FC<TargetProps>;
export type TooltipBodyFC<T> = (props: T) => FC;
export const Tooltip: FC<Props> = ({ Target, Body }) => {
const [hovered, setHovered] = useState(false);
return (
<div
className={styles.traget}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<Target hovered={hovered} />
{hovered && (
<div className={styles.body}>
<Body />
</div>
)}
</div>
);
};
Header.tsx
import { TooltipTargetFC } from "./shared/Tooltip";
import styles from "./Header.module.css";
type Props = {
title: string;
};
export const Header: TooltipTargetFC<Props> =
({ title }) =>
({ hovered }) => {
return (
<div className={styles.title}>
{title} {!hovered && `(マウスオーバーで詳細表示)`}
</div>
);
};
HeaderDetail.tsx
import { TooltipBodyFC } from "./shared/Tooltip";
import styles from "./HeaderDetail.module.css";
type Props = {
description: string;
};
export const HeaderDetail: TooltipBodyFC<Props> =
({ description }) =>
() => {
return <div className={styles.description}>{description}</div>;
};
さいごに
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!