Skip to main content

Що таке fragment у React

React Fragment - це компонент, який групує кілька JSX-елементів в один return без додавання жодного вузла до DOM.

Теорія

TL;DR

  • Аналогія: харчова плівка навколо продуктів - тримає їх разом при переносі, але зникає при розпакуванні, у фінальному HTML її немає
  • <div> додає реальний елемент; Fragment не додає нічого
  • Короткий синтаксис <> і повний <Fragment> дають однаковий результат; тільки <Fragment> приймає key
  • Потрібен один корінь і чистий DOM: Fragment. Потрібен wrapper зі стилями або обробником: <div>

Швидкий приклад

tsx
// div ламає розмітку таблиці function BadRow() { return ( <div> <td>Ім'я</td> <td>Email</td> </div> ); } // DOM: <div><td>Ім'я</td><td>Email</td></div> (невалідний HTML) // Fragment тримає DOM чистим function GoodRow() { return ( <> <td>Ім'я</td> <td>Email</td> </> ); } // DOM: <td>Ім'я</td><td>Email</td>

React вимагає, щоб кожен компонент повертав один кореневий елемент. Fragment виконує цю умову, але в HTML браузера не з'являється.

Головна різниця

<div> вирішує проблему «одного кореня», але додає реальний вузол до DOM. Цей вузол має наслідки: ламає дочірні <td> всередині <tr>, порушує flex і grid, додає зайву вкладеність у семантичний HTML. Fragment вирішує ту саму задачу без жодних витрат на DOM.

Коли що використовувати

  • Кілька сусідніх елементів з одного компонента: Fragment
  • Рядки таблиці або елементи списку, де зайні wrapper-и ламають layout: Fragment
  • Ітерація по масиву з групуванням двох сусідів на елемент: <Fragment key={item.id}>
  • Wrapper потребує className, style або обробник кліку: <div> або семантичний елемент

Порівняльна таблиця

Характеристика<div><Fragment> / <>
Додає вузли до DOM10
Підтримує keyТакТак (тільки повний синтаксис)
Приймає className, styleТакНі
Впливає на layoutТакНі
Коли використовуватиПотрібні стилі або подіїТільки групування

Як це працює

Babel перетворює і <>, і <Fragment> на одне й те саме: React.createElement(React.Fragment, null, ...children). Під час reconciliation React пропускає створення DOM-вузла і монтує дочірні елементи безпосередньо в батьківський вузол. Саме тому обидва синтаксиси дають ідентичний HTML.

Типові помилки

Відсутній key на Fragment у списках:

tsx
// Неправильно: немає key, React попереджає про нестабільні ключі items.map(item => ( <> <li>{item.name}</li> <li>{item.price}</li> </> )); // Правильно: key ставиться на Fragment, а не на дочірні елементи items.map(item => ( <Fragment key={item.id}> <li>{item.name}</li> <li>{item.price}</li> </Fragment> ));

<> не приймає пропси, тому коли потрібен key, переходь на повний <Fragment>.

Очікування, що пропси спрацюють на Fragment:

tsx
// Ігнорується: Fragment не рендериться, style не має ефекту <Fragment style={{ color: 'red' }}> <p>Не застайловано</p> </Fragment>

Кожен проп, крім key, просто ігнорується. Якщо wrapper потребує стилів, використовуй <div>.

Зайва вкладеність:

tsx
// Надлишково: зовнішній Fragment обгортає один дочірній елемент return ( <> <Fragment> <h1>Заголовок</h1> </Fragment> </> ); // Поверни <h1>Заголовок</h1> напряму

Де зустрічається

  • React Table v8: групування <td> всередині <tr> без зайвих вузлів
  • Сторінкові компоненти Next.js: чистий JSX, щоб уникнути hydration mismatch від несподіваних wrapper-div
  • Material-UI: композитні компоненти з сусідніми елементами всередині контейнера
  • Умовний рендеринг сусідніх елементів без накопичення вкладених <div>

Питання на співбесіді

Q: Яка різниця між <Fragment> і <>?
A: Результат однаковий. <> - це скорочення. Повний <Fragment> потрібен тільки для key.

Q: Чи може Fragment мати key?
A: Так, але тільки в повному синтаксисі: <Fragment key="...">. Спроба додати атрибут до <> - синтаксична помилка.

Q: Чому не замінити всі <div> на Fragment?
A: Fragment не підтримує className, style і обробники подій. Коли wrapper сам щось робить, він має бути реальним елементом.

Q: Чи видно Fragment у React DevTools?
A: Так, він відображається як вузол «Fragment» у дереві компонентів. Дочірні елементи залишаються доступними для інспекції.

Q: Як React обробляє Fragment у fiber-дереві під час reconciliation?
A: Fragment - це композитний fiber без host-компонента. Рендерер пропускає createInstance і монтує дочірні елементи безпосередньо в батьківський DOM-вузол. Проп key бере участь у diff-алгоритмі так само, як і на будь-якому іншому елементі.

Приклади

Групування комірок таблиці

Саме тут зайвий <div> найчастіше ламає верстку: layout рипається і не одразу зрозуміло чому, поки не помічаєш невалідний HTML.

tsx
function UserRow({ user }) { return ( <tr> <Fragment key={user.id}> <td>{user.name}</td> <td>{user.email}</td> <td> <button>Edit</button> <button>Delete</button> </td> </Fragment> </tr> ); } // Результат: <tr><td>ім'я</td><td>email</td><td>...</td></tr> // Жодних зайвих вузлів, валідний HTML

key на Fragment забезпечує стабільну реконсиляцію (reconciliation), коли UserRow використовується всередині .map().

Умовний рендеринг сусідніх елементів

tsx
function UserProfile({ user, isAdmin }) { return ( <> <h2>{user.name}</h2> <p>{user.email}</p> {isAdmin && ( <> <p>Роль: Admin</p> <button>Керувати користувачами</button> </> )} </> ); }

Обидва рівні використовують <>. В DOM потрапляють тільки ті елементи, що реально рендеряться, жодного накопичення wrapper-ів.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?