Menu

Fragment в React: <>...</> и <Fragment> с примерами

Fragment в React группирует несколько элементов в одно возвращаемое значение, не добавляя узел на страницу. Короткий синтаксис <>...</>, когда нужен <Fragment key> и почему фрагменты важны в таблицах и списках.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

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 компилируется в одно выражение, поэтому оберните соседей в <>...</>.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

НАЧАТЬ