9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React.ReactNodeと記述者の意図

Last updated at Posted at 2022-04-30

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

何があったの?

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

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

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

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

調べる

React.ReactNodeの中身

// /node_modules/@types/react/index.d.ts 241行目
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;

React.ReactNodeの中身を見てみると、様々な型がユニオンで指定されていました。
そこで一つ疑問が生まれました。

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

React.ReactChildを使ってみる

// /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;
}

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

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

// 親
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 がまとまったものでした。

// /node_modules/@types/react/index.d.ts 233行目
type ReactText = string | number;
// 子供
type Props = {
  children: React.ReactText // OK
}

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

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

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

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

まとめ

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

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


参考

React の props.children の型定義には ReactNode を使う
雰囲気で使わない @types/react


今回のsampleコード
// 環境
"next": "12.1.1",
"react": "17.0.2",
"@emotion/react": "^11.8.2",
"typescript": "4.6.3"

// 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コードの見た目
9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?