React.Fragment — спасение от div-ада или как перестать засорять DOM?
Меня бесят лишние div-ы в коде. Серьёзно. Когда открываю DevTools и вижу матрёшку из бессмысленных обёрток — хочется плакать.
Каждый лишний div — это не просто мусор в DOM, это боль для разработчика и браузера.
Типичная картина в DevTools:
<div class="wrapper">
<div class="container">
<div class="inner">
<div class="content">
<div class="item">
<h2>Заголовок</h2>
<p>Текст</p>
</div>
</div>
</div>
</div>
</div>
Что не так с этим кодом?
Fragment позволяет группировать элементы без создания лишнего DOM-узла.
// Полная запись
import React, { Fragment } from 'react';
function MyComponent() {
return (
<Fragment>
<h1>Заголовок</h1>
<p>Параграф</p>
</Fragment>
);
}
// Короткая запись
function MyComponent() {
return (
<>
<h1>Заголовок</h1>
<p>Параграф</p>
</>
);
}
// С ключом (только полная запись)
function MyList({ items }) {
return (
<>
{items.map(item => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</Fragment>
))}
</>
);
}
// ❌ Плохо — лишний div
function UserInfo({ user }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
<span>{user.role}</span>
</div>
);
}
// ✅ Хорошо — без лишней обёртки
function UserInfo({ user }) {
return (
<>
<h2>{user.name}</h2>
<p>{user.email}</p>
<span>{user.role}</span>
</>
);
}
Результат в DOM:
<!-- С div -->
<div>
<h2>Иван Иванов</h2>
<p>ivan@example.com</p>
<span>Разработчик</span>
</div>
<!-- С Fragment -->
<h2>Иван Иванов</h2>
<p>ivan@example.com</p>
<span>Разработчик</span>
// ❌ Плохо — лишний div при условии
function ConditionalContent({ showDetails, user }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
{showDetails && (
<div> {/* Лишняя обёртка! */}
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
<p>Address: {user.address}</p>
</div>
)}
</div>
);
}
// ✅ Хорошо — чистый DOM
function ConditionalContent({ showDetails, user }) {
return (
<div className="user-card">
<h3>{user.name}</h3>
{showDetails && (
<>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
<p>Address: {user.address}</p>
</>
)}
</div>
);
}
// ❌ Плохо — лишние div-ы в списке
function DefinitionList({ terms }) {
return (
<dl>
{terms.map(term => (
<div key={term.id}> {/* Семантически неверно! */}
<dt>{term.word}</dt>
<dd>{term.definition}</dd>
</div>
))}
</dl>
);
}
// ✅ Хорошо — семантически корректно
function DefinitionList({ terms }) {
return (
<dl>
{terms.map(term => (
<Fragment key={term.id}>
<dt>{term.word}</dt>
<dd>{term.definition}</dd>
</Fragment>
))}
</dl>
);
}
// ❌ Плохо — нарушает структуру таблицы
function TableRows({ items, showTotals }) {
return (
<>
{items.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.price}</td>
</tr>
))}
{showTotals && (
<div> {/* div внутри table — кошмар! */}
<tr>
<td>Итого:</td>
<td>{calculateTotal(items)}</td>
</tr>
<tr>
<td>НДС:</td>
<td>{calculateTax(items)}</td>
</tr>
</div>
)}
</>
);
}
// ✅ Хорошо — корректная структура таблицы
function TableRows({ items, showTotals }) {
return (
<>
{items.map(item => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.price}</td>
</tr>
))}
{showTotals && (
<>
<tr>
<td>Итого:</td>
<td>{calculateTotal(items)}</td>
</tr>
<tr>
<td>НДС:</td>
<td>{calculateTax(items)}</td>
</tr>
</>
)}
</>
);
}
// ❌ Излишество — Fragment для одного элемента
function SingleButton({ onClick, children }) {
return (
<>
<button onClick={onClick}>{children}</button>
</>
);
}
// ✅ Просто и понятно
function SingleButton({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
// ✅ Здесь div нужен для стилизации
function Card({ title, content }) {
return (
<div className="card"> {/* Нужен для CSS */}
<h3 className="card-title">{title}</h3>
<p className="card-content">{content}</p>
</div>
);
}
// ❌ Fragment здесь неуместен
function Card({ title, content }) {
return (
<> {/* Как стилизовать карточку? */}
<h3 className="card-title">{title}</h3>
<p className="card-content">{content}</p>
</>
);
}
// ✅ div нужен для обработки кликов
function ClickableArea({ onAreaClick, children }) {
return (
<div onClick={onAreaClick} className="clickable-area">
{children}
</div>
);
}
// ❌ Fragment не может обрабатывать события
function ClickableArea({ onAreaClick, children }) {
return (
<> {/* onClick не работает! */}
{children}
</>
);
}
До (с лишними div-ами):
function ContactForm() {
const [showAdvanced, setShowAdvanced] = useState(false);
return (
<form className="contact-form">
<div> {/* Лишний div #1 */}
<label>Имя</label>
<input type="text" name="name" />
</div>
<div> {/* Лишний div #2 */}
<label>Email</label>
<input type="email" name="email" />
</div>
{showAdvanced && (
<div> {/* Лишний div #3 */}
<div> {/* Лишний div #4 */}
<label>Телефон</label>
<input type="tel" name="phone" />
</div>
<div> {/* Лишний div #5 */}
<label>Компания</label>
<input type="text" name="company" />
</div>
</div>
)}
<div> {/* Лишний div #6 */}
<button type="submit">Отправить</button>
<button type="button" onClick={() => setShowAdvanced(!showAdvanced)}>
{showAdvanced ? 'Скрыть' : 'Показать'} дополнительные поля
</button>
</div>
</form>
);
}
После (с Fragment):
function ContactForm() {
const [showAdvanced, setShowAdvanced] = useState(false);
return (
<form className="contact-form">
<div className="field-group"> {/* Семантически оправдан */}
<label>Имя</label>
<input type="text" name="name" />
</div>
<div className="field-group"> {/* Семантически оправдан */}
<label>Email</label>
<input type="email" name="email" />
</div>
{showAdvanced && (
<> {/* Группировка без лишнего DOM */}
<div className="field-group">
<label>Телефон</label>
<input type="tel" name="phone" />
</div>
<div className="field-group">
<label>Компания</label>
<input type="text" name="company" />
</div>
</>
)}
<div className="button-group"> {/* Семантически оправдан */}
<button type="submit">Отправить</button>
<button type="button" onClick={() => setShowAdvanced(!showAdvanced)}>
{showAdvanced ? 'Скрыть' : 'Показать'} дополнительные поля
</button>
</div>
</form>
);
}
// Компонент с div
function ItemWithDiv({ title, description }) {
return (
<div>
<h4>{title}</h4>
<p>{description}</p>
</div>
);
}
// Компонент с Fragment
function ItemWithFragment({ title, description }) {
return (
<>
<h4>{title}</h4>
<p>{description}</p>
</>
);
}
Результаты:
{
"rules": {
"react/jsx-fragments": ["error", "syntax"],
"react/jsx-no-useless-fragment": "error"
}
}
{
"jsxBracketSameLine": false,
"jsxSingleQuote": false
}
React.Fragment — это не просто синтаксический сахар. Это инструмент для:
Помните: каждый div должен быть оправдан. Если он не несёт семантической нагрузки и не нужен для стилей — используйте Fragment.
Золотое правило: Не плодите обёртки ради обёрток. DOM должен быть чистым, как ваша совесть.
Хотите больше статей о React и фронтенде? Подписывайтесь на EasyAdvice, добавляй сайт в избранное и прокачивай себя каждый день 💪