【React】Context を使う前に #2 コンポジション (ReactNode 型の Props) を使え 

Props のバケツリレー (Props Drilling) を解決したいからといって、安易に Context を使ったり、状態管理ライブラリ(Recoil, Jotai, Redux)に頼ったりしていませんか？

そんなことをせずとも、「CompA が CompB を使い、CompB が CompC を使い、 CompC が ...」という**依存関係のチェーンを浅くする**のが最善の解決策である場合があります。

**KISS (Keep It Simple Stupid)** という名言があります。暗黙的な連携が発生しない「Props を渡すだけ」のシンプルな関係性を守ることによって、将来のコード読解が楽になり、メンテナンスが容易になり、そもそもバグの混入を防ぐことができます。一石三鳥ですね。

![コンポジションによる依存関係のフラット化](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/82bd3ed9-2560-01f3-da8c-041a3562d35a.png)


前回の記事では「そもそも無駄なコンポーネントを挟まない」テクニックを紹介しましたが、それでも除ききれなかった Props のバケツリレーを削減する方法として、この記事では **コンポジション (composition)**、すなわち ReactNode 型の Props を使う方法を解説します。

---

1\. そもそも無駄なコンポーネントを挟まない

https://qiita.com/honey32/items/b9f70f960e891f031b0f

2\. **コンポジション(ReactNode 型 Props) を使う ← 今ここ**

3\. ステートを適切なコンポーネントに置く

https://qiita.com/honey32/items/2e6206c7dc1974b9bf9a

このうち、 3 については、すでに記事化している内容と重複するので、そちらを参照してください　（バケツリレーには言及していませんが、不要な Props の受け渡しが削られていることが明らかだと思います。）



## Not Good 「OK」と「キャンセル」を一つのコンポーネントに入れると...


![FloatingButtons](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/f4c78167-4679-8300-d79d-32736189ac64.png)

この画像のように、画面の一番下にボタンを２つ配置するコンポーネント `FloatingButtons` を、以下のようなコードで作ってみました。

```tsx
type Props = {
  onCancel: () => void,
  onOk: () => void,
};

const FloatingButtons: FC<Props> = ({
  onCancel,
  onOk,
}) => {
  return (
    <div /*スタイル指定は省略*/ >
      <button type="button" onClick={onCancel}>
        キャンセル
      </button>
      <button type="button" onClick={onOk}>
        OK
      </button>
    </div>
  );
};
```

ワイ「アカン、この FloatingButtons はフォームの送信のときにも使うんやった...」

ワイ「OK ボタンにわたす Props をいじらなアカンな...」



```diff_tsx
  type Props = {
    onCancel: () => void,
+   isOkSubmit?: boolean,
-   onOk: () => void,
+   onOk?: () => void,
  };

  const FloatingButtons: FC<Props> = ({
    onCancel,
+   isOkSubmit = false,
    onOk,
  }) => {
    return (
      <div /* スタイル指定は省略 */ >
        <button type="button" onClick={onCancel}>
          キャンセル
        </button>
-       <button type="button" onClick={onOk}>
+       <button 
+         type={isOkSubmit ? "submit" : "button"}
+         onClick={onOk}
+       >
          OK
        </button>
      </div>
    );
  };
```

これはコードの複雑化、保守性の低下の序章に過ぎません。

「OK ボタンの文言を変えたい」「やっぱりキャンセルボタンは URL で画面遷移したいので Link タグで」「OK ボタンは、押したあと処理が終わるまでクリック不能にしたい」...

現実の仕様を満たせるように作り込んでいくにつれて、ボタンをカスタマイズするための Props が雪だるま式にどんどん増えていき、 FloatingButtons コンポーネントの記述が複雑化しています。

```tsx
// 肥大化した Props の成れの果て ...
type Props = {
  cancelText: string,
  cancelhref?: string,
  onCancel: () => void,
  okText: string,
  okHref?: string,
  isOkDisabled?: boolean
  isOkSubmit?: boolean,
  onOk?: () => void,
};
```

どうすれば良かったのでしょうか？

## OK コンポジションパターン

キャンセルの `<button>` と OK の `<button>` を、それぞれ ReactNode 型の Props に置き換えて、親からそっくり丸ごと注入できるようにしてみましょう。

:::note
ReactNode とは、ざっくり言うと「React に描画させてよい任意の要素」 を表す型であり、以下のような型のユニオンです。

- 描画される要素
  - 例: `<div>ディブ要素</div>`, `<>フラグメント</>` `ほげ`, `23`
- エラーを出さず何も描画されない要素
  - 例: `true`, `false`, `null`, `undefined`

参考: https://dackdive.hateblo.jp/entry/2019/08/07/090000 - 
TypeScript: ReactNode型とReactElement型とReactChild型
:::

```tsx
type Props = {
  negativeButton: ReactNode,
  positiveButton: ReactNode,
};

const FloatingButtons: FC<Props> = ({
  negativeButton,
  positiveButton,
}) => {
  return (
    <div /* スタイル指定は省略 */ >
      {negativeButton}
      {positiveButton}
    </div>
  );
};
```

すると、このコンポーネントを使う側のコードは、以下のようになります。

```tsx
// 使う側はこんな感じ
<FloatingButtons
  negativeButton={(
    <button type="button" onClick={handleCancel}>
      キャンセル
    </button>
  )}
  positiveButton={(
    <button type="button" onClick={handleOK}>
      OK
    </button>
  )}
//  送信ボタンにしたいとき
//  positiveButton={(
//    <button type="submit">OK</button>
//  )}
/>
```

FloatingButtons の責務は「各ボタンをよしなに配置する」ことだけになり、それぞれのボタンの詳細 (a か button か, type="submit", aria タグ, etc. )については知らなくても良くなります。

それぞれのボタンをカスタマイズしたければ、コンポーネントを利用する側で `<button ... ` の JSX 式を編集すればいいだけです。簡単でしょ？

このように、中身の要素をあれこれカスタマイズするために複雑怪奇な Props が必要になってしまった場合には、表示する内容をそのまま ReactNode 型の Props として親から注入するようにすることで Props の複雑な取り扱いを減らすことができます。

![コンポジションによる依存関係のフラット化](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/82bd3ed9-2560-01f3-da8c-041a3562d35a.png)

## OK children で直感的に書けるようになる

コンポジションの話をするときに忘れてはいけないのが children Prop です。

```tsx
<HogeComponent>なかみ</HogeComponent>
```

のような JSX 式を書いたとき、「なかみ」の文字列 (string) は HogeComponent の children Props として渡されます。

なので、下のように children Prop を使うことで、「FloatingButtons の中身」と直感的に理解できるような書き方ができるコンポーネントを作ることができます。

```tsx
type Props = {
  children: ReactNode,
};

const FloatingButtons: FC<Props> = ({
  children
}) => {
  return (
    <div /* スタイル指定は省略 */ >
      {children}
    </div>
  );
};
```

```tsx
// 使う側はこんな感じ
<FloatingButtons>
  <button type="button" onClick={handleCancel}>
    キャンセル
  </button>
  <button type="button" onClick={handleOK}>
    OK
  </button>
</FloatingButtons>
```

## まとめ

前回で説明した「そもそも無駄なコンポーネントを挟まない」では、単純に依存関係のチェーンを短くしましたが、今回の「コンポジション (ReactNode 型 Props) を使う」では、深くなりすぎた依存関係のチェーンを多岐に分かれた浅いチェーンにしてチェーンを浅くしました。

特にコンポジションを使うと、「見た目上は A の中に B が入っているけど、親(P) から A を経由せずに B を直に制御できる」ように書き換えることができます。

つまり、この方法では、 Props のバケツリレーを減らすことができるだけでなく、

- コンポーネントの責務をスリム化し、適切に分担できる
  - たとえば「スタイル上の囲い」と「中身の詳細」
  - 例: FloatingButton の責務は前者だけになりました
- 実は、余計な「親→子」の再レンダリングも減らすことが可能
  - P が再レンダリングすると A, B が強制的に再レンダリングする
  - A が再レンダリングしても、B の再レンダリングが起こらない
    - B 内で context 等を使うと再レンダリングすることも可能
- 実は、「Client Component の下に Server Component を置けない」規則を満たせる
  - ...!?

![コンポジションによる依存関係のフラット化](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234029/82bd3ed9-2560-01f3-da8c-041a3562d35a.png)

## 予告： Server Component とネスト

- 「Server Component では useState のような動的なコードを書けない」
- 「Client Component から Server Component を描画することが出来ない」

 
だから、「Next.js App Router で Server Component を使うと、動的なサイトを作るのが難しくなる」という懸念が広がっているように見受けられます。

しかし、 composition を使うと、 Client Component の中に Server Component を props として差し込むことが出来ることができます。 

これによって、「サーバーと直接やりとりできる」 Server Component の利点と、「状態が変化することができる」 Client Component の利点を組み合わせて自由自在に画面を記述することができるのです。

<del>乞うご期待。</del> 投稿しました。読んでね。 

https://qiita.com/honey32/items/bc24d8c0ea3d096ff956

## 関連スライド、メモ

https://speakerdeck.com/taro28/propsnohaketuriredui-ce-teglobal-statewoshi-usonoqian-ni

https://scrapbox.io/fsubal/props_%E3%81%AE%E3%83%90%E3%82%B1%E3%83%84%E3%83%AA%E3%83%AC%E3%83%BC%E3%81%A3%E3%81%A6%E4%BD%95%E3%81%8C%E6%82%AA%E3%81%84%E3%82%93%E3%81%A0%E3%81%A3%E3%81%91