React Fragmentを使うと、コンポーネントは余分な <div> で包まずに複数の要素を返せます。最初の要素の前に <>、最後の要素の後に </> を書くと、Reactは要素を親の中に直接置き、ページにはフラグメント自身のノードは残りません。
それぞれの Greeting は見出しと段落を並べて返します。<> と </> の行を削除すると、プレビューには代わりにビルドエラーが表示されます。関数が2つの値を返そうとするからです。
コンポーネントにルートが1つ必要な理由
JSXは関数呼び出しをより見やすく書く方法です。<h2>Hi</h2> はおおよそ jsx('h2', { children: 'Hi' }) という1つの式にコンパイルされます。return 文が返せるのは1つの値なので、何にも囲まれていない2つの兄弟タグは return a b と書くようなものです。コンパイラは「Adjacent JSX elements must be wrapped in an enclosing tag」で止まります。
フラグメントが登場する前は、ラッパーの <div> で解決していました。それでも動きますが、divは本物の要素です。DOMに現れ、CSSのレイアウトに参加し、そもそも子として受け付けない親もあります。フラグメントはルートを1つにするルールを満たした後、消えてしまいます。
何も追加されないことは確かめられます。次の例は同じ2つの項目をフラグメントとdivで描画し、それぞれのコンテナの直接の子を数えます。
コンソールには、フラグメントでは2つのspanで 2、divではそれらを包むdivで 1 と出力されます。flexやgridのコンテナでは、この違いによってspanが個別の項目として配置されるか、1つにまとめられるかが決まります。
短い構文とFragmentコンポーネント
フラグメントの書き方は2つあり、どちらも同じものにコンパイルされます。
import { Fragment } from 'react';
// Short syntax, no import needed
<>
<dt>React</dt>
<dd>A library for building user interfaces</dd>
</>
// Long form
<Fragment>
<dt>React</dt>
<dd>A library for building user interfaces</dd>
</Fragment>
ほとんどの場所では <>...</> を使ってください。<Fragment> を使うのは、フラグメントに key が必要なときだけです。短い構文は属性を受け取れないので、<key={id}> は有効なJSXではありません。古いコードでは <React.Fragment> を見かけることもありますが、デフォルトの React インポートを通じて参照した同じコンポーネントです。
リストでのkey付きフラグメント
map でリストを描画するとき、Reactがレンダリング間で追跡できるように、各項目に key が必要です(理由はリストとkeyのページで説明しています)。各項目が1つの要素ではなく要素のグループなら、keyはフラグメントに付けるので、長い形が必要です。
典型的なのは説明リストです。各項目は <dt> と <dd> で、<dl> はそれらを直接含むべきです。
Reverseをクリックすると、各組が独自のkeyを持っているので、組ごと丸ごと入れ替わります。各組をラッパーの <div> で囲んでもkeyは付けられ、dl の中では有効なHTMLですが、フラグメントなら避けられる3つ目の階層がマークアップに入ります。
テーブルの中のフラグメント
余計なラッパーが一番害になるのはテーブルです。<tr> は直接の子として <td> と <th> のセルを期待します。複数のセルを返すコンポーネントがそれを <div> で包むと、ブラウザはdivを1つの奇妙なセルとして扱い、中のセルはヘッダーと揃わなくなります。
フラグメントの行では、価格がPriceの下に、在庫がStockの下にあります。divの行では両方のセルがPriceの列に押し込まれ、Stockの列は空です。開発環境ではReactが <div> は <tr> の子になれないという警告も記録しますが、プレビューは本番ビルドのように動くのでそのチェックは省かれ、ここでは崩れたレイアウトが見えます。サーバーレンダリングでは、ブラウザのHTMLパーサーが場違いなdivを移動させるので、同じ間違いがハイドレーションエラーを起こすこともあります。
同じルールは、<li> 要素を含むべき <ul> や <ol>、<option> 要素を含むべき <select> にも当てはまります。そうした親の中に直接置くべき部品をコンポーネントが返すときは、フラグメントを返してください。
フラグメントか配列か
コンポーネントは要素の配列を返すこともでき、Reactはフラグメントと同じように順番に描画します。Reactは配列をリストとして扱うので、返す配列の各要素には map の出力と同じく独自のkeyが必要です。
function Cells() {
return [<td key="price">$4</td>, <td key="stock">In stock</td>];
}
これでも動きますが、より煩雑です。要素の間にカンマが入り、それぞれにkeyが必要で、マークアップが大きくなると角かっこを見落としやすくなります。決まった要素のグループにはフラグメントを使い、配列はデータを map した結果という本来の用途に取っておきましょう。
フラグメントにできないこと
フラグメントは要素を描画しないので、クラス、スタイル、イベントハンドラ、refを付ける場所がありません。<Fragment className="row"> は何の役にも立たず、Reactがフラグメントから読むpropは key だけです。グループにスタイルを付けたり、クリックを受け取ったりしたいなら、結局は本物の要素が必要です。それで問題ありません。行をまとめる <tbody> やリスト項目を入れる <li> のように、その場所で有効な要素を選んでください。
フラグメントは要素が使える場所ならどこでも使えます。条件付きレンダリングの中でも使え、{show && <>...</>} はグループ全体を一度に表示したり隠したりします。
よくある質問
ReactのFragmentとは何ですか?
Fragmentは、DOMノードを追加せずにコンポーネントが複数の要素を1つの値として返せるようにするラッパーです。<><h1 /><p /></> は h1 と p を直接親の中に置きます。
<>と<Fragment>の違いは何ですか?
作られるものは同じです。<>...</> は短い構文です。<Fragment> は react からインポートする長い形で、key を受け取れるのはこちらだけです。リストでフラグメントを描画するときに必要になります。
divではなくFragmentを使うのはなぜですか?
divは本物の要素です。直接の子を前提とするレイアウト(flexやgridのコンテナ、ul、table、tr)を壊すことがあり、DOMに余計なものを足します。Fragmentは何も追加しません。
FragmentにclassNameやonClickを付けられますか?
いいえ。Fragmentは要素を描画しないので、クラス、スタイル、イベントハンドラを付ける場所がありません。受け取れるpropは key だけです。それらが必要なら本物の要素を使ってください。
"Adjacent JSX elements must be wrapped in an enclosing tag" というエラーが出るのはなぜですか?
コンポーネントが、何にも囲まれていない兄弟要素を2つ返したからです。JSXは1つの式にコンパイルされるので、兄弟要素を <>...</> で囲んでください。