9歳娘「パパのコンポーネント、条件だらけで大変だね！」

# ある日の我が家

ワイ「よっしゃ、商品を表示するためのコンポーネントができたでぇ」

娘「見せて見せて！」

```diff_tsx:ProductCard.tsx
type Props = {
  name: string
  price: number
}

export const ProductCard: React.FC<Props> = ({ name, price }) => {
  return (
    <div className="card">
      <h3>{name}</h3>
      <p>{price}円</p>
    </div>
  )
}
```

ワイ「↑こんな感じや」
ワイ「商品名と価格を表示する、シンプルなコンポーネントや！」

娘「へぇ〜」
娘「商品一覧ページとかで、こんなふうに並べて使う感じ？」

```diff_tsx:ProductList.tsx
export const ProductList: React.FC = () => {
  return (
    <div className="list">
      <ProductCard name="りんご" price={100} />
      <ProductCard name="みかん" price={80} />
      <ProductCard name="バナナ" price={120} />
    </div>
  )
}
```

ワイ「そうそう」
ワイ「ええやろ？」

娘「うん！シンプルでいいと思う！」

# しかし別のページでは「説明文」も表示したい

ワイ「あかん！」
ワイ「おすすめ商品一覧ページでは、商品の説明文も表示せなアカンかったわ！」
ワイ「ほな、説明文もpropsに追加せんとな！」

```diff_tsx:ProductCard.tsx
type Props = {
  name: string
  price: number
+ description?: string
}

export const ProductCard: React.FC<Props> = ({ 
  name, 
  price,
+ description,
}) => {
  return (
    <div className="card">
      <h3>{name}</h3>
      <p>{price}円</p>
+     {description && <p>{description}</p>}
    </div>
  )
}
```

ワイ「完璧や！」
ワイ「説明文は必須じゃないから、オプショナルや！」
ワイ「そんで条件に応じて描画してやるんや」
ワイ「これで、おすすめ商品一覧ページでも使えるでぇ！」

娘「うん！」

```diff_tsx:RecommendList.tsx
export const RecommendList: React.FC = () => {
  return (
    <div className="recommend-list">
      <ProductCard
        name="りんご"
        price={100}
        description="甘みが強く、みずみずしい食感が特徴です！"
      />
    </div>
  )
}
```

ワイ「これでええな！」

# さらに別のページでは「割引率」を表示したい

ワイ「あかん！」
ワイ「セール商品一覧ページでは、割引率を表示せなアカンかったわ！」

娘「じゃあ、またpropsと条件分岐を追加するの？」

ワイ「せや！」

```diff_tsx:ProductCard.tsx
type Props = {
  name: string
  price: number
  description?: string
+ discountRate?: number
}

export const ProductCard: React.FC<Props> = ({ 
  name, 
  price,
  description,
+ discountRate,
}) => {
  return (
    <div className="card">
      <h3>{name}</h3>
      <p>{price}円</p>
      {description && <p>{description}</p>}
+     {discountRate && (
+       <p className="discount">{discountRate}%OFF!</p>
+     )}
    </div>
  )
}
```

ワイ「おお、ええ感じや！」
ワイ「これでセール商品一覧ページでも使えるな！」

```diff_tsx:SaleList.tsx
export const SaleList: React.FC = () => {
  return (
    <div className="list">
      <ProductCard
        name="りんご"
        price={100}
        discountRate={20}
      />
      <ProductCard
        name="みかん"
        price={80}
        discountRate={30}
      />
    </div>
  )
}
```

ワイ「↑これでええな！」

# ボタンを表示したいページもある

ワイ「あかん！」
ワイ「別のページでは、カートに追加するボタンを表示せなあかん！」

娘「じゃあ、またpropsを追加するの？」
娘「propsがい〜っぱいになってきたけど・・・」

```diff_tsx:ProductCard.tsx
type Props = {
  name: string
  price: number
  description?: string
  discountRate?: number
+ showAddToCartButton?: boolean
+ onAddToCart?: ButtonProps["onClick"]
}

export const ProductCard: React.FC<Props> = ({ 
  name, 
  price,
  description,
  discountRate,
+ showAddToCartButton,
+ onAddToCart,
}) => {
  return (
    <div className="card">
      <h3>{name}</h3>
      <p>{price}円</p>
      {description && <p>{description}</p>}
      {discountRate && (
        <p className="discount">{discountRate}%OFF!</p>
      )}
+     {showAddToCartButton && (
+       <Button onClick={onAddToCart}>
+         カートに追加
+       </Button>
+     )}
    </div>
  )
}
```

ワイ「おお、ええやん！」
ワイ「これでカート追加ボタンが必要なページでも使えるな！」

```diff_tsx:AnotherList.tsx
export const AnotherList: React.FC = () => {
  return (
    <div className="list">
      <ProductCard
        name="りんご"
        price={100}
        showAddToCartButton
        onAddToCart={() => {
          // カートに追加する処理
        }}
      />
    </div>
  )
}
```

ワイ「これでええな！」

# ボタンの見た目もカスタマイズしたい

ワイ「あかん！」
ワイ「あるページでは、ボタンの`variant`も指定せなアカンわ」

娘「え・・・まだpropsを追加するの？」

```diff_tsx:ProductCard.tsx
type Props = {
  name: string
  price: number
  description?: string
  discountRate?: number
  showAddToCartButton?: boolean
  onAddToCart?: ButtonProps["onClick"]
+ buttonVariant?: ButtonProps["variant"]
}

export const ProductCard: React.FC<Props> = ({ 
  name, 
  price,
  description,
  discountRate,
  showAddToCartButton,
  onAddToCart,
+ buttonVariant = 'primary',
}) => {
  return (
    <div className="card">
      <h3>{name}</h3>
      <p>{price}円</p>
      {description && <p>{description}</p>}
      {discountRate && (
        <p className="discount">{discountRate}%OFF!</p>
      )}
      {showAddToCartButton && (
        <Button 
+         variant={buttonVariant}
          onClick={onAddToCart}
        >
          カートに追加
        </Button>
      )}
    </div>
  )
}
```

娘「う〜ん」
娘「パパ、これって`Button`のpropsを2つもバケツリレーしてるね」
娘「商品コンポーネントに、ボタンのvariantを渡すなんて、なんか変な感じ・・・」
娘「JSX部分も条件だらけで、パパ以外の人が読む時に大変かも・・・」

ワイ「せやな・・・」
ワイ「こんなコンポーネント、ワイも保守したくないわ・・・」
ワイ「でもさ・・・」
ワイ「ページによって差し込みたいパーツが違うんやもん・・・」

娘「確かに・・・」

ワイ「いろんな表示パターンがあるから、もういっそ」
ワイ「こうしてまうか・・・？」

```tsx
// 商品コンポーネントは、枠だけ
export const ProductCard: React.FC<PropsWithChildren> = ({ 
  children
}) => {
  return (
    <div className="card">
      {children}
    </div>
  )
}
```

娘「なるほどね」
娘「もう、枠だけにしちゃって」
娘「好きな`children`を渡して使ってください、って感じね」

ワイ「そうや」

娘「でも、基本的な商品情報とか、大体のスタイルは同じだからなぁ」
娘「色んなページで、似たような`children`を何度も書くことになりそう」

ワイ「ぐぬぬ・・・」

娘「・・・そうだ！」
娘「**コンポジション**でやってみようよ」

ワイ「・・・と言いますと？」

娘「えっと、今回の場合で言うと」
娘「カスタマイズしたい部分だけReactNodeで渡せばいいんじゃないかな？って」

# コンポジションでやってみる

娘「こうすればいいと思う！」

```diff_tsx:ProductCard.tsx
type Props = {
  name: string
  price: number
+ headerSlot?: React.ReactNode
+ bodySlot?: React.ReactNode
+ footerSlot?: React.ReactNode
}

export const ProductCard: React.FC<Props> = ({ 
  name, 
  price,
+ headerSlot,
+ bodySlot,
+ footerSlot,
}) => {
  return (
    <div className="card">
+     {headerSlot}
      <h3>{name}</h3>
      <p>{price}円</p>
+     {bodySlot}
+     {footerSlot}
    </div>
  )
}
```

ワイ「おお！」
ワイ「なるほど！」
ワイ「カスタマイズしたい部分だけを、ReactNodeで渡せるようにしたんやな！」

娘「うん！」
娘「これなら、基本の商品情報部分とかスタイルは統一できて」
娘「カスタマイズしたい部分だけを自由に変更できるよ！」

娘「例えば、おすすめ商品一覧ページで、説明文だけ追加したい時は・・・」

```tsx
// おすすめ商品一覧ページの場合
<ProductCard
  name="りんご"
  price={100}
  bodySlot={<RecommendDescription>
    甘みが強く、みずみずしい食感が特徴です！
  </RecommendDescription>}
/>
```

娘「セール商品一覧ページで、割引バッジだけ追加したい時は・・・」

```tsx
// セール商品一覧ページの場合
<ProductCard
  name="みかん"
  price={80}
  headerSlot={<SaleBadge>
    期間限定20%OFF
  </SaleBadge>}
/>
```

娘「ショップページで、カートに追加ボタンだけ追加したい時は・・・」

```tsx
// ショップページの場合
<ProductCard
  name="バナナ"
  price={120}
  footerSlot={<AddToCartButton 
    variant="secondary"
    onClick={() => console.log("clicked!")}
  />}
/>
```

ワイ「なるほど！」
ワイ「必要な部分だけをカスタマイズできて、シンプルやな！」
ワイ「propsのバケツリレーもなくなっとるし！」

# まとめ

- 「このページでは、これも表示したい！」という要望に沿ってpropsを増やしていくと・・・
    - 少し表示内容を変えたいだけなのに、propsがどんどん増えていく
    - JSXも条件分岐だらけになり、意図が読み取りづらくなる
    - 別コンポーネントのpropsまでバケツリレーすることに
- そんな時は**コンポジション**でやってみよう
    - **Composition**とは「構成」「組み立て」という意味
    - クソデカコンポーネントにpropsをたくさん渡すのではなく、小さなコンポーネントを組み合わせて構成する
    - カスタマイズしたい部分はReactNodeで受け取る
      - 以下の方法もある
        - Render Propsパターン
        - コンポーネントをpropsとして渡す（高階コンポーネント）
    - 共通部分は統一しつつ、柔軟な実装が可能になる

娘「↑こういうことだね！」

ワイ「せやな！」
ワイ「娘ちゃんありがとう！」

**〜おしまい〜**