React では複数要素を返すときにラッパーが必要になる
React でコンポーネントを作っていると、1つのコンポーネントは必ず「単一の要素」を返す必要がある、というルールに必ずぶつかります。
例えばこんなコードは「返すのは1つの要素にしてください」と怒られてしまいます。
// NG例:return 内に複数要素
return (
<h1>Hello</h1>
<p>World</p>
);
ありがちな <div>
乱立問題
実際のプロジェクトでは、この制約を回避するためにとりあえず <div>
でラップするパターンをよく見かけます。
return (
<div>
<h1>Hello</h1>
<p>World</p>
</div>
);
一見問題なさそうに見えますし動くのですが、プロジェクトが大きくなると <div>
だらけになり、「div地獄」 に陥ります。
特に UI ライブラリや CSS でスタイリングしているとき、不要な <div>
が DOM に増えていくと、
- 余計なスペースや余白が発生する
- CSS セレクタが複雑になる
- テーブルやリスト構造で HTML の仕様に違反する(今回のような
<tr>
が<div>
の下に来るエラー)
といった問題が出てきます。
実際に テーブル行をループで生成する場面で <div>
を入れてしまい、validateDOMNesting
の警告が出た経験があります。
React.Fragmentとは
公式ドキュメントによるとReact.Fragment は「DOM に余分なノードを追加せずに子要素をグループ化できるコンポーネント」です。
つまり、複数要素をまとめたいけど <div>
など余計なタグを増やしたくないときに使います。
イメージ的には「見えない箱」みたいな存在です。
<React.Fragment>
と省略記法 <>...</>
の違い
React では Fragment を書く方法が2つあります。
- フル表記:
return (
<React.Fragment>
<h1>Hello</h1>
<p>World</p>
</React.Fragment>
);
- 省略記法(ショートハンド):
return (
<>
<h1>Hello</h1>
<p>World</p>
</>
);
どちらも同じ意味ですが、違いは key
を使えるかどうかです。
-
<React.Fragment key={...}>
→ OK -
<>{...}</>
→key
をつけられない
そのため、リストレンダリング時など key
が必要な場面ではフル表記を使うのがベストです。
DOM に出力されないことを確認できるサンプル
実際に Fragment を使ったときの DOM 出力を見てます。
function Sample() {
return (
<>
<h1>Hello</h1>
<p>World</p>
</>
);
}
このコンポーネントをレンダリングすると、ブラウザの DevTools には次のように表示されます。
<h1>Hello</h1>
<p>World</p>
余計な <div>
や <span>
は一切出ていません。
もしこれを <div>
でラップしていたら、こうなります。
<div>
<h1>Hello</h1>
<p>World</p>
</div>
わずかな違いですが、これが積み重なると DOM が肥大化したり、CSS やレイアウトに悪影響を与える原因になります。
よくある使いどころ
テーブル構造での validateDOMNesting
エラー回避
HTML の仕様では <tbody>
直下には <tr>
しか書けません。
プロジェクトでも、ループ処理の中で <div>
を挟んでしまい、以下のような警告が出ました。
Warning: validateDOMNesting(...): <tr> cannot appear as a child of <div>.
修正前(エラーになる例):
<Table.Tbody>
<div> {/* tbody直下にdivはNG */}
<Table.Tr>...</Table.Tr>
</div>
</Table.Tbody>
修正後(Fragmentで解決):
<Table.Tbody>
<>
<Table.Tr>...</Table.Tr>
</>
</Table.Tbody>
DOMに余計なタグを挟まないので、テーブル構造を壊さずに済むのが大きな利点です。
コンポーネントの return で兄弟要素を返したいとき
特に小さな UI 部品を作るとき、return 内で複数要素を返したくなることがあります。
Fragment があればラップ用のタグを置かずに実現できます。
function LabelAndInput() {
return (
<>
<label>名前</label>
<input type="text" />
</>
);
}
応用パターン
key
が必要な場面での利用
map
で複数要素を返すとき、key
を指定しないと React が警告を出します。
このときショートハンド <>...</>
では key
をつけられないため、フル表記の<React.Fragment>
を使う必要があります。
// NG:ショートハンドではkeyを使えない
list.map((item) => (
<>
<li>{item.title}</li>
<li>{item.value}</li>
</>
));
// OK:React.Fragmentならkeyが使える
list.map((item) => (
<React.Fragment key={item.id}>
<li>{item.title}</li>
<li>{item.value}</li>
</React.Fragment>
));
条件分岐の返却をシンプルにする
三項演算子や条件分岐で複数要素を返したいときにも便利です。
{isLoggedIn ? (
<>
<p>ログイン中</p>
<button>ログアウト</button>
</>
) : (
<>
<p>ゲスト</p>
<button>ログイン</button>
</>
)}
ライブラリとの相性
UI ライブラリ(Mantine, Material UI など)は内部で厳格な DOM 構造を要求することがあります。
特に <table>
/ <ul>
/ <select>
系で <div>
を挟むとエラーになるケースは多いので、
「とりあえずFragmentでくくる」が鉄則になります。
注意点
ショートハンドでは key
が使えない
先ほども触れましたが、省略記法 <>...</>
では key
をつけられません。
リストレンダリングなど key
が必須な場面では必ず <React.Fragment key={...}>
を使うようにしましょう。
props は渡せない
Fragment 自体は 純粋にラッパー用途なので、className
や style
などの属性を渡すことはできません。
もしスタイリングが必要なら、素直に <div>
や <span>
を使うのが正解です。
過剰に使わない
あるあるですが、便利だからといって過剰に使うと逆にコードが読みにくくなります。
「DOMに不要なタグを出したくない」ときにだけ使うのがベストです。
まとめ
-
React.Fragment は「見えないラッパー」
DOM に余計なノードを増やさずに複数要素をまとめられる。-
div地獄
を回避してDOMをクリーンに保つ - テーブル構造などで
validateDOMNesting
エラーを防ぐ - コンポーネントやリストで複数要素を返したいとき
-
key
が必要な場合は<React.Fragment key={...}>
- 条件分岐やUIライブラリとの組み合わせでDOM構造を壊さない
-