Главная / Статьи / Фрагменты React и StrictMode: более лаконичная разметка и более раннее обнаружение ошибок

Фрагменты React и StrictMode: более лаконичная разметка и более раннее обнаружение ошибок

Узнайте, как фрагменты React группируют элементы без дополнительных оболочек DOM, и как двойное вызов в режиме StrictMode, предназначенном исключительно для разработки, выявляет нечистые операции отрисовки и эффекты.

976 слов

Два функционала 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 включенным; удвоенная запись логов и удвоение эффектов являются преднамеренными диагностическими мерами.
  • Исправляйте проблему двойного выполнения эффектов с помощью правильной очистки, а не путем изменения зависимостей.