React.ReactNodeと記述者の意図

今回は `ReactNodeによる型定義` について調べました。

## 何があったの？

`子`で受け取る型をどうすればよいのか悩んでいました。
調べていく中で、どうやら `React.ReactNode` を使うのがよいとわかりました。

```ts
type Props = {
  children: React.ReactNode // ここで使う
}
const Kodomo: VFC<Props> = ({ children }) => { ~~
```

この指定にて、
`バインド要素 'children' には暗黙的に 'any' 型が含まれます。` 
といったようなエラーが消えて嬉しかったのを覚えています。

そこで、 `ReactNode` とは何なのかを少し掘り下げてみることにしました。

## 調べる

### React.ReactNodeの中身

```ts
// /node_modules/@types/react/index.d.ts 241行目
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
```
React.ReactNodeの中身を見てみると、様々な型がユニオンで指定されていました。
そこで一つ疑問が生まれました。

なるほど、子の型に`ReactNode`って付けておけば、一旦大丈夫そうだな。
でも、子供に渡すモノよっては`ReactNode`を使うのは<b>大袈裟すぎない？</b>
もっと型を絞ったほうが記述者の意図が伝わりやすく、後からメンテしやすいはずでは。

### React.ReactChildを使ってみる

```ts
// /node_modules/@types/react/index.d.ts 234行目辺り
type ReactChild = ReactElement | ReactText; // ここをみてみる
type ReactFragment = {} | Iterable<ReactNode>;
interface ReactPortal extends ReactElement {
  key: Key | null;   
  children: ReactNode;
}
```

`ReactChild` も `ReactElement | ReactText` がユニオンで指定されていました。
ということで早速、「`string`しか受け取らない子」を作って、試してみます！ 
(一番下にSampleコード一式置いておきます)

`React.ReactNode` でも `React.ReactChild` どちらでもエラーが無いはずです。

```tsx
// 親
const Oya: NextPage = () => {
  return (
    <div>
      <Kodomo>
        子供で表示するテキスト
      </Kodomo>
    </div>
  )
}

// 子供 (sampleのためinport無しの直書き)
type Props = {
  // children: React.ReactNode // OK
  children: React.ReactChild // OK
}
const Kodomo = ({ children }: Props) => {
  return <span>{children}</span>
}
```

エラー無く通りました。これで、1つ制作者の意図に近づきました！！
が、、、`ReactElementとReactText` の両方使う想定なのだろうか？と疑問を残しそうです。

後からこのコードをお世話するどなたか(自分含む)ためにも、わかりやすくしておきたい。

### React.ReactTextを使ってみる

`ReactText` はシンプルです。 `string | number` がまとまったものでした。

```ts
// /node_modules/@types/react/index.d.ts 233行目
type ReactText = string | number;
```

```tsx
// 子供
type Props = {
  children: React.ReactText // OK
}
```

エラーなし！
`文字か数字を出すためだけに作ったシンプルなコンポーネント` という制作者の意図が伝わってきました！


、、、で、本当は`文字か数字`どっちを出したかったんだい？

```ts
type Props = {
  children: string // OK
  children: number // NG
  // 'Kodomo' コンポーネントには、テキストを子要素として指定できません。JSX のテキストには 'string' 型が含まれていますが、'children' の予期された型は 'number' です。
}
```

結局こうなりました。
`string`しか受け取らない子 の時は `string` だけを渡せばOKです。
制作者の意図が<b>完全に</b>伝わってきました。

## まとめ

汎用的に使う子コンポーネント、型が決まっている子コンポーネント色々ありますが、
意図を伝え残すためにも型定義を`React.ReactNode`だけに頼らないほうがよいという考えです。


ここは改善した方がいいよ！こういった考え方もあるよ！というご意見はあるかと思います！
ぜひコメントください！今後の参考にいたします！

---

##### 参考
[React の props.children の型定義には ReactNode を使う](https://maku.blog/p/xenv4bh/#reactelement--jsxelement)
[雰囲気で使わない @types/react](https://qiita.com/Takepepe/items/0bbe4ab6ff4858a4e186)

---

##### 今回のsampleコード

```
// 環境
"next": "12.1.1",
"react": "17.0.2",
"@emotion/react": "^11.8.2",
"typescript": "4.6.3"
```

```jsx

// Next.js 

import type { NextPage } from 'next'
import { VFC } from 'react'
import { css } from "@emotion/react"

const styles = {
  padding: (size: number) => css`
    padding: ${size / 10}rem;
  `,
  baseInner: css`
    border: 1px solid #000;
  `,
}

// 親
const Oya: NextPage = () => {
  return (
    <div css={[styles.baseInner, styles.padding(6)]}>
      <Kodomo>
        ReactElement
      </Kodomo>
    </div>
  )
}

// 子供
type Props = {
  children: React.ReactNode // or ReactChild or ReactText or string
}
const Kodomo: VFC<Props> = ({ children }) => {
  return <span>{children}</span>
}

export default Oya

```

##### sampleコードの見た目

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206790/ad550caf-78ea-f698-3db5-1010af913441.png" width="300">