Fragment в React позволяет компоненту вернуть несколько элементов, не оборачивая их в лишний <div>. Напишите <> перед первым элементом и </> после последнего, и React поместит элементы прямо в родителя, без собственного узла на странице.
Каждый Greeting возвращает заголовок и абзац рядом. Удалите строки <> и </>, и превью вместо этого покажет ошибку сборки, потому что функция теперь пытается вернуть два значения.
Почему компоненту нужен один корень
JSX это более удобный способ писать вызовы функций. <h2>Hi</h2> компилируется примерно в jsx('h2', { children: 'Hi' }), одно выражение. Инструкция return может вернуть одно значение, поэтому два соседних тега без обёртки похожи на return a b: компилятор останавливается с ошибкой "Adjacent JSX elements must be wrapped in an enclosing tag".
До появления фрагментов решением был <div>-обёртка. Это работает, но div это настоящий элемент. Он появляется в DOM, участвует в CSS-раскладке, а некоторые родители вообще не принимают его как потомка. Фрагмент выполняет правило одного корня и затем исчезает.
Можно убедиться, что он ничего не добавляет. Пример ниже рендерит одни и те же два элемента через фрагмент и через div, а затем считает прямых потомков каждого контейнера.
Консоль выводит 2 для фрагмента, то есть два span, и 1 для div, который их оборачивает. Во flex- или grid-контейнере эта разница решает, будут ли span расположены как отдельные элементы или собраны в один.
Короткий синтаксис и компонент Fragment
Фрагмент можно написать двумя способами, и они компилируются в одно и то же:
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 по умолчанию.
Фрагменты с ключом в списках
Когда вы рендерите список через map, каждому элементу нужен key, чтобы React мог отслеживать его между рендерами (почему, объясняет страница о списках и ключах). Если каждый элемент это группа элементов, а не один, ключ ставится на фрагмент, поэтому нужна длинная форма.
Классический случай это список описаний: каждая запись состоит из <dt> и <dd>, и <dl> должен содержать их напрямую.
Нажмите Reverse, и пары поменяются местами целиком, потому что у каждой пары свой ключ. <div>-обёртка вокруг каждой пары тоже приняла бы ключ, и это валидный HTML внутри dl, но она добавляет в разметку третий уровень, которого фрагмент избегает.
Фрагменты в таблицах
В таблицах лишняя обёртка вредит больше всего. <tr> ожидает ячейки <td> и <th> как прямых потомков. Если компонент, который возвращает несколько ячеек, обернёт их в <div>, браузер воспримет div как одну странную ячейку, и ячейки внутри перестанут совпадать с заголовком.
В строке с фрагментом цена стоит под Price, а остаток под Stock. В строке с div обе ячейки втиснуты в колонку Price, а колонка Stock пуста. В разработке React также выводит предупреждение, что <div> не может быть потомком <tr>; превью работает как продакшен-сборка, поэтому пропускает эту проверку, и здесь вы видите сломанный макет. При серверном рендеринге та же ошибка может вызвать и ошибки гидратации, потому что HTML-парсер браузера переносит неуместный div.
То же правило действует для <ul> и <ol>, которые должны содержать элементы <li>, и для <select>, который должен содержать элементы <option>. Всякий раз, когда компонент возвращает части, которые должны лежать прямо внутри такого родителя, возвращайте фрагмент.
Фрагменты или массивы
Компонент может вернуть и массив элементов, и React отрендерит их по порядку, как фрагмент. Каждому элементу в возвращаемом массиве нужен свой ключ, как результату map, потому что React считает массив списком:
function Cells() {
return [<td key="price">$4</td>, <td key="stock">In stock</td>];
}
Это работает, но шумнее: запятые между элементами, ключ на каждом и скобка, которую легко пропустить, когда разметка растёт. Используйте фрагмент для фиксированной группы элементов, а массивы оставьте для того, чем они являются естественно: результатом обхода данных.
Чего фрагмент не умеет
Раз фрагмент не рендерит элемент, ему некуда поставить класс, стиль, обработчик события или ref. <Fragment className="row"> ничего полезного не делает, и единственный пропс, который React читает у фрагмента, это key. Когда нужно стилизовать группу или слушать на ней клики, всё-таки нужен настоящий элемент, и это нормально: выберите тот, что валиден на своём месте, например <tbody> для группировки строк или <li> для пункта списка.
Фрагмент работает везде, где работает элемент, включая условный рендеринг: {show && <>...</>} показывает или скрывает всю группу сразу.
Часто задаваемые вопросы
Что такое Fragment в React?
Fragment это обёртка, которая позволяет компоненту вернуть несколько элементов одним значением, не добавляя узел DOM. <><h1 /><p /></> помещает h1 и p прямо в родителя.
Чем <> отличается от <Fragment>?
Они дают одно и то же. <>...</> это короткий синтаксис. <Fragment> это длинная форма, которую импортируют из react, и только она может принимать key, который нужен, когда вы рендерите фрагменты в списке.
Зачем использовать Fragment вместо div?
div это настоящий элемент. Он может сломать макеты, которые ожидают прямых потомков (flex- и grid-контейнеры, ul, table, tr), и добавляет шум в DOM. Fragment не добавляет ничего.
Может ли у Fragment быть className или onClick?
Нет. Fragment не рендерит элемент, поэтому повесить класс, стиль или обработчик события не на что. Единственный пропс, который он принимает, это key. Если нужно что-то из этого, используйте настоящий элемент.
Почему я получаю ошибку "Adjacent JSX elements must be wrapped in an enclosing tag"?
Компонент вернул два соседних элемента, вокруг которых ничего нет. JSX компилируется в одно выражение, поэтому оберните соседей в <>...</>.