🔷

なぜ<React.Fragment>を書かなければならないのか

2024/08/05に公開

JSXと単一ルートの制約

JSXのコンポーネントには、単一のルート要素を返さなければいけないという制約があります。

単純にコンポーネントを<div>で囲むなどの方法で対処できることもありますが、flexboxの中の要素をコンポーネント化していて<div>が追加されることによりスタイルが崩れる場合などにはそういった対処が難しいこともあります。

DOMに直接影響を与えないようにするには、<Fragment>、もしくはその省略記法の<>を利用します。

// Fragmentを用いて2つの<li>要素をレンダーする
function Example() {
  return (
    <>
      <li>Item 1</li>
      <li>Item 2</li>
    </>
  );
}

単一ルートの制約の背景

そもそも、なぜこういった単一要素の制約があるのか?という事についてはReactの公式ドキュメントで言及されています。

JSX は HTML のように見えますが、裏ではプレーンな JavaScript オブジェクトに変換されます。関数から 2 つのオブジェクトを返したい場合、配列でラップしないといけませんよね。2 つの JSX タグを返したい場合に別のタグかフラグメントでラップしないといけないのも、同じ理由です。
https://ja.react.dev/learn/writing-markup-with-jsx#why-do-multiple-jsx-tags-need-to-be-wrapped

ReactにとってのJSXは、createElement(type, props, ...children)のシンタックスシュガーです。
例えば先程の例は、以下のような単一のオブジェクトを返すJavascriptに変換されます。

// createElementを用いて前回の例と同じDOMを描画する
function Example() {
  return React.createElement(
    React.Fragment,
    null,
    React.createElement("li", null, "Item 1"),
    React.createElement("li", null, "Item 2")
  );
}

以下のような不正なJSXは、1つの関数で2つのオブジェクトを同時に返さないと成立しないため妥当なJavascriptに変換することが不可能、ということがなんとなく理解できるかと思います。

// 2つの要素を返す不正なJSX
function Example() {
  return (
    <li>Item 1</li>
    <li>Item 2</li>
  );
}

Fragment実装の経緯

Fragment実装の経緯を見てみましょう。

FragmentのようなAPIを追加する要望は、2014年頃から挙がっていましたが、一つのコンポーネントが複数の要素を返すことに対する若干の懸念や実装レベルでの困難さがあり、中々リリースには至っていませんでした。

その3年後となる2017年、React16のリリースでReactがレンダーできる値に配列が追加されました。(#10783)
たとえば、以下は配列を返す妥当なコンポーネントです。

// 配列を返すJSX
function Example() {
  return (
    [
      // 配列なのでキーが必要
      <li key="1">Item 1</li>,
      <li key="2">Item 2</li>
    ]
  );
}

この仕様が追加されたことによって、ひとつのコンポーネントで複数の要素を返す挙動が可能になりました。ただし、配列の括弧などを記述する必要があり通常のJSXとはかなり違う記法になってしまうため混乱を招く可能性があります。

それを解決する形でReact16.2で登場したのがFragmentです。
<Fragment>で囲うことによって中の要素を配列のように扱いつつ、一方で通常のJSXの記法で描画することが可能になりました。複数の要素をまとめたい、というFragmentのシンプルな目的からKeyも必須ではなくなっています。

なぜFragmentが必要なのか

なぜReactの利用者が毎回<Fragment>を書かなければならないのか、というのが僕がFragmentについて調べるきっかけになった疑問でした。できればJSXのコンパイラが自動で追加するなどの形を取ってほしいですよね。

State of React 2023を見た感じだと、同じことを思っている人はそれなりにいるみたいです。

Couldn't React just add it if it detects multiple children without one?
https://2023.stateofreact.com/en-US/features/#all_features

ただ、この疑問に関する明確な答えは得ることができませんでした。
なので、個人的な仮説を書いておこうと思います。

仮説

これは<Fragment>を自動で追加する動作が暗黙的すぎる、ということに尽きると思います。

まず、ひとつの要素がひとつのオブジェクトに変換されるJSXの記法に違反していてもコンパイルに成功してしまう、というのは混乱を招く要素の1つになります。

また、Fragmentが自動で付与されることによる不具合が起きることも考えられます。
たとえば、<><><Item /></></>のように2階層以上Fragmentをネストした場合、DOM上は同じ位置にあるコンポーネントでStateのリセットが行われます。Fragmentが暗黙的に追加されることによってこの現象が起こった場合、デバッグを行うことが困難になることは容易に想像できます。

つまり、Reactは明示的なコードを守るために、ユーザーに手動で<Fragment>を追加させている、というのが仮説です。

もし望むなら、自動でFragmentを付けるBabelのプラグインを自前で書くこと自体は可能なはずなので試してみるのもいいかもしれません。

終わりに

読んでいただきありがとうございました。
もし誤っている箇所等あれば指摘していただければ幸いです。

X (Twitter): @koyo_k0

Discussion