Фрагменты React и StrictMode: более лаконичная разметка и более раннее обнаружение ошибок
Узнайте, как фрагменты React группируют элементы без дополнительных оболочек DOM, и как двойное вызов в режиме StrictMode, предназначенном исключительно для разработки, выявляет нечистые операции отрисовки и эффекты.
Два функционала React, которые никогда не рисуют что-либо на экране, тем не менее формируют основу практически каждой иерархии компонентов: фрагменты и StrictMode. Фрагменты позволяют сохранять DOM без элементов-обёрток, существующих лишь для соблюдения структуры JSX, в то время как StrictMode намеренно создаёт дополнительную нагрузку на компоненты во время разработки, чтобы проблемы с логикой проявлялись раньше, чем их заметят пользователи.
Почему JSX нужен единый корневой элемент
JSX преобразует каждый тег в вызов функции, поэтому два соседних тега, возвращаемых вместе, представляют собой два значения там, где ожидается только одно. Это приводит к ошибке компиляции:
return (
<h1>Hello</h1>
<p>Welcome</p>
);
Традиционным решением было обёртывание этих тегов в div:
return (
<div>
<h1>Hello</h1>
<p>Welcome</p>
</div>
);
Дополнительный узел имеет свои недостатки: он увеличивает глубину DOM, нарушает логику flex- или grid-макетов, требующих прямых дочерних элементов, усложняет работу с селекторами и может привести к недействительному или менее доступному HTML.
Группировка без обёртки
Fragment объединяет дочерние элементы в React без создания каких-либо элементов DOM. Его краткая синтаксис — это пара пустых тегов:
return (
<>
<h1>Hello</h1>
<p>Welcome</p>
</>
);
Явная форма React.Fragment делает то же самое и требуется, когда необходимо передать свойство:
return (
<React.Fragment>
<h1>Hello</h1>
<p>Welcome</p>
</React.Fragment>
);
Генерируемый HTML содержит только элементы h1 и p. Повышение производительности минимально; настоящая польза заключается в правильном форматировании маркировки.
Возвращение элементов-братьев
Компонент, создающий несколько элементов одного уровня, может оставить вопросы макетирования родителю:
function Card() {
return (
<>
<h2>Title</h2>
<p>Description</p>
</>
);
}
Фрагменты с ключами в списках
При обработке данных, где каждый элемент генерирует более одного элемента, React всё равно требует стабильный key для каждого элемента. Краткая синтаксис <> не поддерживает атрибуты, поэтому используйте React.Fragment с ключом:
items.map(item => (
<React.Fragment key={item.id}>
<h2>{item.title}</h2>
<p>{item.description}</p>
</React.Fragment>
));
Ячейки и строки таблиц
У таблиц HTML есть строгая модель содержимого: элемент tr может содержать только элементы td или th. Использование элемента div в качестве оболочки здесь недопустимо. Фрагмент позволяет компоненту добавить несколько ячеек в строку, принадлежащую его родителю:
function Row() {
return (
<>
<td>A</td>
<td>B</td>
</>
);
}
Что проверяет StrictMode
StrictMode не отрисовывает ничего, не добавляет элементов и не оказывает влияния на версии приложения для производства. В режиме разработки он включает дополнительные проверки, в том числе:
- предупреждения о методах жизненного цикла устаревших классов, считающихся небезопасными, таких как
componentWillMount - предупреждения о устаревших API, таких как ссылки на строки и
findDOMNode - двукратное вызов функций компонента, инициализаторов и обновлений для выявления нечистой отрисовки
- запуск операций через дополнительные циклы настройки, очистки и повторной настройки при монтировании для выявления пропущенных операций очистки
Точный список менялся в разных версиях React, поэтому проверьте актуальную документацию для вашей версии.
Целенаправленная двойная отрисовка
Рассмотрим компонент, который выводит логи во время отрисовки:
function App() {
console.log("Rendered!");
return <h1>Hello</h1>;
}
В режиме StrictMode во время разработки в консоли отображается сообщение дважды:
Rendered!
Rendered!
Это сделано намеренно. Функция отрисовки должна быть чистой: при одинаковых параметрах и состоянии она возвращает одинаковый результат и не изменяет ничего за пределами себя. Двойное вызов такой функции приводит к нарушениям, таким как изменение общей переменной, что создаёт явно неверные результаты. Чистота кода важна, поскольку одновременная отрисовка может начинаться, приостанавливаться, прерываться или повторяться, и код, предполагающий ровно одну отрисовку на обновление, не будет работать в таких условиях.
Включение режима
Оберните дерево, которое нужно проверить, обычно всё приложение, у корня:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Последствия использования StrictMode: массив зависимостей — не решение проблемы
Вот пример эффекта без массива зависимостей. Он выполняется после каждой отрисовки, поэтому каждое обновление запускает новый запрос:
useEffect(() => {
console.log("Fetching...");
fetch("/api");
});
Добавление пустого массива ограничивает выполнение эффекта моментом его монтирования:
useEffect(() => {
console.log("Fetching...");
fetch("/api");
}, []); // stable dependency
Такое изменение верно, но оно не предотвращает двойной запрос в режиме разработки. Начиная с React 18, StrictMode монтирует компонент, выполняет его функцию очистки, а затем снова монтирует его, поэтому эффект с [] всё равно выполняется дважды. Двойная отрисовка — это сигнал, а не ошибка. Настоящее решение — функция очистки, которая делает вторую отрисовку безвредной, обычно прерывая первый запрос с помощью AbortController или игнорируя его результат с помощью флага. Такой эффект также безопасен при реальном повторном монтировании в продакшене.
Сравнение
- Цель: Фрагменты группируют элементы;
StrictModeвыявляет ошибки. - Результат в DOM: ни один из них не добавляет элементы.
- Влияние на производственную среду: никакого для обоих.
- Поведение во время выполнения: фрагменты отображаются нормально;
StrictModeдважды вызывает процессы отрисовки и применения эффектов только во время разработки. - Преимущества: более чистый и корректный маркапп по сравнению с предсказуемым кодом, защищённым от побочных эффектов.
Минимальная конфигурация для тестирования
Приведённый ниже компонент возвращает два сестринских элемента через фрагмент:
export default function App() {
return (
<>
<h1>Hello World</h1>
<p>Rendered using Fragments</p>
</>
);
}
Файл входных данных отображает его внутри StrictMode, поэтому любая нечистая логика, добавленная позже, сразу обнаруживается во время разработки:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Основные выводы
- Используйте фрагмент каждый раз, когда элемент-обёртка существует лишь для соответствия структуре JSX, и применяйте
React.Fragmentс атрибутомkeyв списках.
StrictMode включенным; удвоенная запись логов и удвоение эффектов являются преднамеренными диагностическими мерами.