【実務・中級編】 React.ReactElement型の詳細 – React実践ガイド

Reactの型定義、なんとなく`React.ReactNode`で済ませていないか?

現場のコードレビューをしていると、Propsの型定義で思考停止してとりあえず `children: React.ReactNode` と書いているケースによく遭遇する。もちろん、それが「動く」ことは知っている。だが、それが「何を許容し、何を排除しているのか」を言語化できないまま使うのは、大規模開発においては時限爆弾になりかねない。

今日は、Reactの型定義の中でも特に混同されがちな `React.ReactNode` と `React.ReactElement` の境界線、そして「特定のコンポーネントのみをPropsで受け取りたい」という現場の切実な要求を、どう型システムで制圧するかについて語ろう。

—

1. React.ReactNode vs React.ReactElement:その本質的な違い

まず、この二つは「階層」が違うことを理解してほしい。

  • `React.ReactNode`: 最も広義な型だ。`string`, `number`, `boolean`, `null`, `undefined` はもちろん、`ReactElement` やそれらの配列まで全てを飲み込むブラックホールのような型だ。`children` のような「何が来るか予測できない場所」には最適だが、逆に言えば「何でもあり」すぎて安全ではない。
  • `React.ReactElement`: これは「Reactコンポーネントが生成したオブジェクトそのもの」だ。具体的には `{ type, props, key, ref }` を持つ、Reactがレンダリングのために解釈する最小単位のオブジェクトを指す。要するに、`
    ` や `` のような「JSXのインスタンス」だ。

ブラウザの裏側で何が起きているか?
Reactは、最終的にこれらの型を「Fiber」と呼ばれる内部データ構造に変換する。`ReactNode` を受け取った場合、Reactは「これが文字列なのか、配列なのか、あるいはコンポーネントなのか」をレンダリングのたびに確認(`reconcile`)する必要がある。型を厳格に指定することは、単なる静的解析の喜びではなく、意図しないデータ構造の混入を未然に防ぎ、レンダリングの予測可能性を高めるための防波堤なんだ。

—

2. 特定のコンポーネントのみを許可したいという「欲」

例えば、「`Layout` コンポーネントには `Header` か `Footer` しか入れたくない」という制約をかけたい場合、`ReactNode` では役不足だ。ここで登場するのが、`ReactElement` をベースにした特定の型指定だ。

実務で使える、型安全なパターンを見てみよう。

import React, { ReactElement } from ‘react’;

// 1. 許可したいコンポーネントのProps型を定義
type HeaderProps = { title: string };
const Header = (_props: HeaderProps) =>

Header

;

// 2. 特定のコンポーネントのみを受け取るためのユーティリティ型
// ReactElementのtype属性を特定のコンポーネント型に固定する
type StrictChildren = ReactElement;

interface LayoutProps {
// childrenをReactElement型に制限する
children: StrictChildren;
}

const Layout = ({ children }: LayoutProps) => {
return (

{/ ここで安全にchildrenをレンダリングできる /}
{children}

);
};

// 使用例
const App = () => (

{/ これはOK /}

{/ これはコンパイルエラーになる!(型安全の勝利) /}
{/

不正な要素

/}

);

—

3. なぜ現場でこれが必要なのか?

「別に動けばいいじゃん」という声が聞こえてきそうだが、中級者からシニアへ上がるためには、「自分が書いたコンポーネントの仕様を、コンパイラに強要できるか」が分かれ道になる。

例えば、`children` に渡された要素の `props` を親側で操作したいときがあるだろう。`ReactNode` だと、`children` が文字列や `null` である可能性を考慮して条件分岐を書かなければならず、コードが汚れる。

しかし、`ReactElement` に絞り込んでいれば、`children.props.title` のように、型安全に子要素のプロパティへアクセスできる。これは、コンポーネントの疎結合を守りつつ、親子間の契約(Contract)を型レベルで固定する、非常に強力なテクニックだ。

最後に:チーフアーキテクトからのアドバイス

実務においては、以下の基準で使い分けるのが正解だ。

1. 汎用的なラッパーコンポーネント(CardやModalなど):`React.ReactNode` を使え。柔軟性こそが正義だ。
2. 特定の責務を持つコンポーネント(ListとListItem、LayoutとHeaderなど):`React.ReactElement` を使い、型の壁を作れ。

Reactは自由だ。自由だからこそ、型という「規律」をどこに敷くかで、そのプロダクトの寿命が決まる。今日紹介した `ReactElement` の制限テクニックを、ぜひ次のPRで試してみてほしい。君の書くコンポーネントが、より堅牢で、他のメンバーにとって「予測可能な」ものになるはずだ。

困ったことがあれば、またいつでも聞きに来てくれ。現場からは以上だ。

コメント

タイトルとURLをコピーしました