React.ReactNodeの正体:なぜ「とりあえずこれ」と指定するのか、その深淵を覗く
現場でコードをレビューしていると、コンポーネントのPropsに `children: React.ReactNode` を指定しているコードによく出会います。初心者を卒業したエンジニアの多くが「とりあえず `ReactNode` にしておけばエラーが出ないから」という理由でこれを使っています。
しかし、なぜ `ReactElement` や `JSX.Element` ではなく、あえて `ReactNode` なのか。その境界線を曖昧にしたまま開発を続けるのは、例えるなら「中身が何か分からない箱を、中身を確認せずに運び続ける」ようなものです。
今日は、Reactの型定義におけるこの「究極の汎用型」の正体と、現場で後悔しない使い分けの極意を伝授します。
—
React.ReactNode が許容する「広すぎる世界」
まず、結論から言います。`ReactNode` は、Reactがレンダリングできるあらゆるものを内包するユニオン型です。
type ReactNode =
| ReactElement // JSXタグそのもの
| number // 数値
| ReactFragment // <>>
| ReactPortal // createPortalで生成されたもの
| boolean // true/false(何もレンダリングされないが型としては許容される)
| null // 何も表示しない
| undefined // 何も表示しない
| ReactNodeArray // 上記の配列
これを見て分かる通り、`ReactNode` は「画面に表示されうる最小単位から、複雑なコンポーネントツリーまで」を飲み込むブラックホールのような型です。
ブラウザの裏側で何が起きているか
Reactのレンダリングプロセスにおいて、`ReactNode` は最終的にFiberツリーというReact独自の内部構造へ変換されます。Reactは、この「何でもあり」の型を再帰的に走査し、テキストノードなのか、DOMノードなのかを判定して、DOM API(`createElement` や `textContent` の更新)を叩きます。
もし型定義を `string` に限定してしまうと、親コンポーネントがアイコンを渡した瞬間にTypeScriptの静的解析で弾かれます。「柔軟なコンポーネント設計」を目指すなら、この懐の深さこそが武器になります。
—
実務で「ReactNode」を使うべき場面と、避けるべき場面
現場での設計思想として、以下の基準を設けてみてください。
1. 基本は `ReactNode` で良い(レイアウトコンポーネント)
`Container` や `Card` のような、中身に何が入るか依存しないコンポーネントの場合、`ReactNode` は最適解です。
2. 「厳密さ」が必要な場合は型を絞る
例えば「特定のコンポーネント(例: `MenuItem`)しか受け取りたくない」という場合、`ReactNode` は緩すぎます。その場合は `ReactElement` を使いましょう。
実践的なサンプルコード
以下のコードは、現場ですぐに使える汎用的なレイアウトコンポーネントの例です。
import React from ‘react’;
type LayoutProps = {
// コンポーネントを囲むための標準的な定義
children: React.ReactNode;
// タイトルは文字列のみに限定したい場合
title: string;
};
export const Card = ({ children, title }: LayoutProps) => {
return (
{title}
ここではどんなReactNodeも受け入れられる。
テキスト、JSX、さらには配列やnullまで安全に処理される。
/}
{children}
);
};
—
「ReactElement」との決定的な違い
たまに `children: React.JSX.Element` と書く人がいますが、これはおすすめしません。
- `ReactElement`: オブジェクト形式のJSX(`{ type: ‘div’, props: {…} }`)のみ。つまり `string` や `number` を直接渡すことができません。
- `ReactNode`: 上記の通り、`string` や `number` も含みます。
もし `ReactElement` を使ってしまうと、親コンポーネントで以下のような記述をした瞬間にTypeScriptが吠えます。
// ❌ children: ReactElement を期待している場合
こんにちは! {/ stringはReactElementではないためエラー! /}
現場のUI開発において、テキストを直接コンポーネントに渡すことは頻繁に発生します。「文字列も一つのノードである」というReactの哲学を理解していれば、`ReactNode` がなぜ標準なのか、納得できるはずです。
—
シニアからの最後のアドバイス
Reactにおける型定義は、「何を許容し、何を禁止するか」という設計意図そのものです。
- 迷ったら `ReactNode`:柔軟性を最大化できます。
- 意図があるなら絞る:特定のUIパターンを強制したいなら `ReactElement` や `ReactNode[]` を検討してください。
「型はドキュメントである」とよく言われます。`children: React.ReactNode` と書くことは、「このコンポーネントは、Reactが扱えるどんな表現でも歓迎しますよ」という、開発者に対する意思表示なのです。
この「広さ」の正体を理解した上で、自信を持って型を使い分けてください。あなたの書くコードが、他のチームメンバーにとって読みやすく、そして壊れにくいものになることを願っています。

コメント