ReactNodeのすすめ - とりあえずstring型を指定するのはやめよう!

2024/08/20 11:28

リストやテーブルなどのコンポーネントを実装している時、propsに色々なパラメータを指定できるようにしたりしてメンテナンスに苦労することがよくありました。そんな時、Reactの内部でchildrenの型指定はどうなっているんだろうと思って調べてみました!

TL;DR

  • ReactNode型は、レンダリングできる全てのデータのが含まれています
  • 具体的な型は ReactElement | string | number | Iterable<ReactNode> | ReactPortal | boolean | null | undefined
  • childrenなどの表示するパラメータで型を制限したい理由がない場合、 children: ReactNode; と指定するようにする

ReactNodeを調べたきっかけ

管理画面のテーブルの開発をしている時、セルごとに表示を切り替える時にどうしようか悩んでいました。
(管理画面を開発したことがあるエンジニアなら共感してくれるはず!)

propsでパラメータを渡してセルのコンポーネントで分岐させることもできますが、コンポーネントのメンテが大変になるので表示だけで複雑な処理はさせたくないなと思っていました。
ページなどの段階で表示を指定し、コンポーネントでは表示する枠だけにできないかなと思っていたところ、childrenにコンポーネントを渡してデータを整形するタイミングで表示を指定できないかなと思いつきました! そこで、childrenがどうなっているんだろうと思い調べ始めました。

ReactNodeとは?

公式っぽいドキュメントでは、こう説明されています。

ReactNodeは、Reactがレンダリングできるものを記述する型である。

パラメータ
ReactNodeはパラメータを取りません。

使用方法
ReactNodeの最も一般的な使用例は、childrenの型付けです。

import { ReactNode } from "react";

interface Props {
  children?: ReactNode;
}

function Component({ children }: Props) {
  return children;
}

<Component>は、Reactがレンダリングできるものなら何でも子として受け入れます。以下に例を示します:

function Examples() {
  return (
    <>
      <Component>
        <div>Hello</div>
      </Component>
      <Component>Hello</Component>
      <Component>{123}</Component>
      <Component>
        <>Hello</>
      </Component>
      <Component>{true}</Component>
      <Component>{null}</Component>
      <Component>{undefined}</Component>
      <Component>{[1, 2, 3]}</Component>
    </>
  );
}

つまり、文字列とかHTML要素とかの表示できそうなのはReactNode型にまとめられていて、childrenでとりあえず使っておけばOKってことです!
(うん、分かりやすい!)

実際に含まれている型としては、 ReactElement | string | number | Iterable<ReactNode> | ReactPortal | boolean | null | undefined があります。

解説
ReactElementHTML要素やコンポーネント<p>Hello World!</p>
string文字列'Hello World!'
number数字0, 1
Iterable<ReactNode>配列[0, 1, 2]
ReactPortalモーダルなどで使用するPortalcreatePortal(

Hello World!

, document.body)
boolean真偽true, false
nullnull
undefined不明な値undefined

Object型やDate型などはそのまま使用できませんが、string型に変換すれば表示できます。

実際にReactNodeを使ってみる!

上の公式サイトの使用方法にもありますが、Propsの中で使用するのが一般的内容です。
個人的には React.FC をよく使うので、ちょっとだけ変えています。

child.tsx

import type React from 'react';

interface Props {
  label: React.ReactNode;
  children: React.ReactNode;
}

export const Child: React.FC<Props> = ({ label, children }) => {
  return (
    <div className="Child">
      <p className="Child__label">{label}<p>
      <p className="Child__data">{children}<p>
    </div>
  );
};

parent.tsx

import { Child } from './child';

import type React from 'react';

export const Parent: React.FC<Props> = ({ label, children }) => {
  return (
    <div className="Parent">
      <Child label="ラベル">データ</Child>
      <Child label={<>ラベル<span>*</span></>}>
        <bold>データ</bold>
      </Child>
    </div>
  );
};

こうすれば、labelやchildrenに複雑なデータを渡すことができてpropsのパラメータを減らせるだけではなく、コンポーネントにロジックを含めずにシンプルにすることができます!

ReactNodeに似た型について

ReactText

ReactNodeとは違い、コードを読む人にテキストだけですと伝えるために使います。
実際に含まれている型は string | number です。

ReactChild

ReactNodeとは違い、コードを読む人にテキストや簡単な子要素だけですと伝えるために使います。
実際に含まれている型は ReactElement | string | number です。

まとめ

ReactNode型は便利ですが、string型などを指定することでコードを読む人に意図を伝えることができます。
「ここのパラメータは今後どのように使われそうか?」「パラメータじゃなくてReactNode型にすることでメンテしやすくならないか?」など、きちんと考えながらコードを書くのが大事だなと改めて思いました。