1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React.FragmentでDOMをクリーンに保つ

Posted at

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つあります。

  1. フル表記:
return (
  <React.Fragment>
    <h1>Hello</h1>
    <p>World</p>
  </React.Fragment>
);
  1. 省略記法(ショートハンド):
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 自体は 純粋にラッパー用途なので、classNamestyle などの属性を渡すことはできません。
もしスタイリングが必要なら、素直に <div><span> を使うのが正解です。

過剰に使わない

あるあるですが、便利だからといって過剰に使うと逆にコードが読みにくくなります。
「DOMに不要なタグを出したくない」ときにだけ使うのがベストです。


まとめ

  • React.Fragment は「見えないラッパー」
    DOM に余計なノードを増やさずに複数要素をまとめられる。
    • div地獄を回避してDOMをクリーンに保つ
    • テーブル構造などで validateDOMNesting エラーを防ぐ
    • コンポーネントやリストで複数要素を返したいとき
    • key が必要な場合は <React.Fragment key={...}>
    • 条件分岐やUIライブラリとの組み合わせでDOM構造を壊さない
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?