なぜFragmentが必要なのか
Reactを勉強していると、次のような書き方を見かけることがあります。
<>
<h1>タイトル</h1>
<p>説明</p>
</>
あるいは、
<React.Fragment>
<h1>タイトル</h1>
<p>説明</p>
</React.Fragment>
最初は、
- なぜわざわざこんな書き方をするのか
-
divで囲めばよいのではないか -
mapの中でなぜkeyが出てくるのか
と疑問に思いやすいです。
この記事では、ReactのFragment(フラグメント)について、次の流れで整理します。
- Fragmentとは何か
- なぜ必要なのか
- 不要な
divがあると何が困るのか -
mapで要素を描画するときにkeyが必要な理由 - 配列で返す場合との違い
Fragmentとは何か
ReactのFragmentは、複数の要素をひとまとめにしたいが、実際のDOMには余計な要素を追加したくないときに使う仕組みです。
たとえば、次のようなコンポーネントを考えます。
function Sample() {
return (
<>
<h1>タイトル</h1>
<p>説明文です</p>
</>
);
}
このとき、Reactの中ではh1とpをひとまとまりとして扱えますが、実際のHTMLには余計なラッパー要素は追加されません。
つまりFragmentは、
「React内部ではまとめるが、DOMには出力しない入れ物」
と考えると分かりやすいです。
なぜFragmentが必要なのか
Reactでは、コンポーネントのreturnで複数の要素をそのまま並べて返すことはできません。
たとえば、次のコードはエラーになります。
function Sample() {
return (
<h1>タイトル</h1>
<p>説明文です</p>
);
}
そのため、このようにdivで囲む書き方がよく使われます。
function Sample() {
return (
<div>
<h1>タイトル</h1>
<p>説明文です</p>
</div>
);
}
ただし、この方法だと不要なdivがDOMに追加されることがあります。
そこでFragmentを使います。
function Sample() {
return (
<>
<h1>タイトル</h1>
<p>説明文です</p>
</>
);
}
不要なdivがDOMに追加されると何が困るのか
一見すると、divで囲んでも特に問題なさそうに見えます。
しかし、実際にはいくつか不都合があります。
1. レイアウトが崩れることがある
たとえば、Flexboxで子要素を横並びにしたい場合を考えます。
<div className="container">
<div>
<Item />
<Item />
</div>
</div>
.container {
display: flex;
}
この場合、.container の直接の子は内側のdivだけになります。
本当はItemを横並びにしたかったのに、余計なdivが入ったせいで意図したレイアウトにならないことがあります。
2. CSSセレクタが効かなくなることがある
たとえば、次のようなCSSがあります。
ul > li {
color: red;
}
この > は子セレクタで、
「ulの直下の子であるliだけに適用する」という意味です。
もしReact側で余計なdivを挟むと、
<ul>
<div>
<li>りんご</li>
</div>
</ul>
このliはulの直下ではなくなるため、ul > li が効かなくなります。
ちなみに、次のようにスペースで書くと意味が変わります。
ul li {
color: red;
}
こちらはulの中にあるすべてのliが対象になるため、
この場合は途中にdivがあってもスタイルは適用されます。
ただし、ul > liのように「直下の子のみ」を対象とするセレクタを使っている場合、余計な要素があるとスタイルが適用されなくなる点に注意が必要です。
3. HTMLの意味構造が崩れることがある
ulの直下には本来liが来るべきです。
しかし、間にdivを挟むと、HTMLの意味構造として不自然になります。
<ul>
<div>
<li>項目</li>
</div>
</ul>
このような構造は、アクセシビリティや保守性の面でも望ましくありません。
4. DOMノードが増える
不要な要素が増えると、DOM構造が深くなります。
1つ1つは小さな差でも、画面全体では無駄になりやすいです。
Fragmentの書き方
Fragmentには主に2つの書き方があります。
1. 短縮記法
function Sample() {
return (
<>
<h1>タイトル</h1>
<p>説明文です</p>
</>
);
}
もっともよく使う形です。
2. 明示的な書き方
function Sample() {
return (
<React.Fragment>
<h1>タイトル</h1>
<p>説明文です</p>
</React.Fragment>
);
}
または、次のようにも書けます。
import { Fragment } from "react";
function Sample() {
return (
<Fragment>
<h1>タイトル</h1>
<p>説明文です</p>
</Fragment>
);
}
短縮記法とReact.Fragmentの違い
見た目はほとんど同じですが、1つ重要な違いがあります。
短縮記法 <>...</> には key を付けられません。
一方で、React.Fragment には key を付けられます。
<React.Fragment key={item.id}>
<h2>{item.title}</h2>
<p>{item.desc}</p>
</React.Fragment>
この違いが重要になるのが、mapでリストを描画するときです。
補足:mapの中でkeyが出てくる理由
ここで注意したいのは、keyはFragmentに特別に必要なものではなく、
「リストとして描画される要素」に必要なものだという点です。
たとえば、複数の記事データがあるとします。
const items = [
{ id: 1, title: "React", desc: "UIライブラリ" },
{ id: 2, title: "Vue", desc: "JavaScriptフレームワーク" },
];
これを表示するとき、次のように書くことがあります。
items.map((item) => (
<React.Fragment key={item.id}>
<h2>{item.title}</h2>
<p>{item.desc}</p>
</React.Fragment>
));
なぜkeyが必要なのか
Reactは再レンダリング時に、
- どの要素が残ったのか
- どの要素が削除されたのか
- どの要素が追加されたのか
を判別して差分更新します。
このとき、各要素を識別するためにkeyが使われます。
たとえば、前回が次のようなリストだったとします。
[A, B, C]
次回がこうなった場合、
[B, C, D]
Reactは、Aが消えてDが追加されたことを判断したいわけです。
その識別の手がかりになるのがkeyです。
なぜFragmentにkeyを付けるのか
この例では、1件分の表示内容が
h2p
の2要素セットになっています。
つまり、mapの返り値としては、この2つをひとまとまりとして1要素扱いしたいわけです。
そのため、一番外側のFragmentにkeyを付けます。
items.map((item) => (
<React.Fragment key={item.id}>
<h2>{item.title}</h2>
<p>{item.desc}</p>
</React.Fragment>
));
子要素にkeyを付ければよいわけではない
たとえば、次のような書き方は適切ではありません。
items.map((item) => (
<>
<h2 key={item.id}>{item.title}</h2>
<p>{item.desc}</p>
</>
));
この場合、keyが付いているのはh2だけです。
しかしReactが識別したいのは、h2単体ではなく、h2とpを含むまとまり全体です。
そのため、mapの返り値の一番外側にkeyを付ける必要があります。
Fragmentと配列returnの違い
Reactでは、複数要素を返す方法として配列を使うこともできます。
function Sample() {
return [
<h1 key="title">タイトル</h1>,
<p key="desc">説明文です</p>,
];
}
これでも動きます。
ただし、Fragmentを使う場合とは少し意味合いが違います。
配列returnの特徴
- 各要素に
keyが必要 - リスト的な扱いになる
- JSXとしては少し読みにくい
Fragmentの特徴
- 複数要素を自然にひとまとまりにできる
- 通常は
key不要 - JSXとして読みやすい
単に「複数要素をまとめたいだけ」の場合は、Fragmentのほうが自然です。
なぜReactは1つのまとまりを返させるのか
Reactは、画面をコンポーネントの木構造として管理しています。
たとえば、
function Sample() {
return (
<>
<h1>タイトル</h1>
<p>説明文です</p>
</>
);
}
というコードも、React内部では「Sampleコンポーネントが1つのまとまりを返している」として扱います。
もし何のまとまりもなく複数要素をバラバラに返せてしまうと、Reactが内部で扱う木構造や差分更新の考え方が複雑になります。
そのため、コンポーネントは1つのルートとなるまとまりを返す形になっています。
Fragmentは、そのまとまりをDOMに余計な要素を出さずに実現するための仕組みです。
使い分けの目安
実務や学習では、次のように考えると分かりやすいです。
-
複数要素をまとめたいが、DOMに余計な要素を増やしたくない
- Fragmentを使う
-
mapで複数要素のセットを返したい-
React.Fragment key={...}を使う
-
-
本当にHTML上でもラッパー要素が必要
-
divやsectionなど意味のある要素を使う
-
つまり、divは何でも囲むための道具ではなく、
DOM上で意味やレイアウト上の役割が必要なときに使うものです。
まとめ
ReactのFragmentは、複数要素をひとまとめにしつつ、DOMには余計な要素を追加しないための仕組みです。
ポイントを整理すると、次のようになります。
- Reactではコンポーネントが1つのまとまりを返す必要がある
-
divで囲むと不要なDOM要素が増えることがある - Fragmentを使えば、DOMを汚さずに複数要素をまとめられる
-
mapの中でkeyが必要なのは、リスト要素を識別するため - 複数要素のセットを返すときは、外側のFragmentに
keyを付ける - 配列returnも可能だが、通常はFragmentのほうが読みやすい
最初は「謎の記法」に見えますが、Fragmentは
React内部の都合と、実際のDOM構造のきれいさを両立するための道具
と理解すると整理しやすいです。
おわりに
ReactのFragmentは見た目が地味ですが、理解しておくと
- JSXの書き方
- DOM構造の設計
-
mapとkey - Reactの差分更新の考え方
まで一緒に整理しやすくなります。
自分も最初は「divでよくない?」と思っていましたが、
CSSやHTML構造まで考えるとFragmentの意味が見えてきました。
同じように学習中の方の参考になればうれしいです。