Фрагменти React та StrictMode: більш лаконічний маркап та раннє виявлення помилок
Дізнайтеся, як фрагменти React групують елементи без додаткових обгорток DOM, та як подвійне викликання StrictMode, призначене лише для розробки, виявляє нечисті процеси відображення та ефекти.
Дві функції React, які ніколи не відображають жодного контенту на екрані, проте впливають майже на кожну структуру компонентів: Fragments та StrictMode. Fragments забезпечують чистоту 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!
Це є частиною дизайну. Функція відображення має бути чистою: при однакових props та стані вона повинна повертати однаковий результат та не змінювати нічого поза собою. Два виклики можуть призвести до порушень, таких як зміна спільної змінної, що створює помилкові результати. Чистота коду важлива, оскільки паралельне відображення може починатися, зупинятися, відкидатися або повторюватися, і код, який передбачає саме одне відображення за оновленням, не буде працювати за таких умов.
Увімкнення режиму
Обгорніть дерево, яке потрібно перевірити, зазвичай всю програму, біля кореня:
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 або ігноруючи його результат за допомогою флага. Такий ефект також є безпечним під час справжньої переініціалізації в продакшені.
Поруч
- Мета: Fragments групують елементи;
StrictModeвиявляє помилки. - Вихід DOM: жоден з них не додає елементи.
- Вплив на продакшн: жодного для обох.
- Поведінка під час виконання: Fragments відображаються звичайно;
StrictModeподвійно викликає процеси відображення та ефекти лише під час розробки. - Що ви отримуєте: чистіший, коректний маркап порівняно з прогнозованим кодом, захищеним від побічних ефектів.
Мінімальна налаштовка для тестування
Компонент нижче повертає два братські елементи через Fragment:
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>
);
Основні висновки
- Використовуйте Fragment щоразу, коли елемент-обгортка існує лише для задоволення вимог JSX, та використовуйте
React.Fragmentзkeyу списках.
StrictMode увімкненим; подвоєні журнали та ефекти є навмисними засобами діагностики.