Пошук уроків, статей та іншого контенту
Освоїмо React DevTools Profiler для вимірювання тривалості рендерингу та пошуку повільних компонентів.
React DevTools Profiler — це інструмент для вимірювання продуктивності React-застосунку. Він допомагає визначити:
скільки часу займає рендеринг компонентів;
які компоненти беруть участь у повільному оновленні;
які компоненти перерендерюються без потреби;
як змінюється продуктивність після оптимізації.
Profiler вимірює не час завантаження сторінки загалом, а роботу React під час конкретних оновлень інтерфейсу.
React DevTools встановлюється як розширення для браузера. Після встановлення відкрийте DevTools браузера клавішою F12 або комбінацією:
Windows та Linux: Ctrl + Shift + I;
macOS: Cmd + Option + I.
Серед вкладок DevTools з’являться вкладки Components і Profiler.
Якщо вкладка Profiler відсутня:
перевірте, чи застосунок запущений у режимі розробки;
переконайтеся, що React DevTools під’єднався до сторінки;
перезавантажте сторінку після встановлення розширення;
перевірте, що застосунок справді використовує React.
Щоб виміряти конкретну дію:
Відкрийте вкладку Profiler.
Натисніть кнопку запису.
Виконайте дію в застосунку: введіть текст, натисніть кнопку або відкрийте список.
Зупиніть запис.
Проаналізуйте отриманий профіль.
Під час запису React DevTools збирає інформацію про оновлення React-компонентів.
Профілюйте одну конкретну дію, а не всю сесію користувача. Так легше зрозуміти, що саме спричинило затримку.
Наприклад, замість запису всього запуску застосунку краще окремо перевірити:
введення символу в поле пошуку;
відкриття модального вікна;
перемикання вкладки;
оновлення одного елемента списку.
React оновлює інтерфейс не безперервно, а окремими операціями. Кожна така операція називається commit.
Commit може виникнути після:
зміни стану через setState;
зміни значень props;
оновлення контексту;
зміни зовнішніх даних, які використовує компонент.
Після запису профілю вгорі або внизу панелі з’являються commit. Вибравши один із них, можна побачити:
тривалість оновлення;
компоненти, які рендерилися;
внесок кожного компонента в загальний час;
компоненти, які не рендерилися під час цього commit.
Важливо розрізняти:
рендеринг React — обчислення того, яким має бути інтерфейс;
commit — застосування результату до DOM.
Profiler переважно показує витрати React на підготовку оновлення. Це не завжди дорівнює повному часу, який користувач бачить на екрані.
Основне представлення профілю — Flamegraph.
У ньому кожен прямокутник відповідає React-компоненту:
ширина показує відносну тривалість роботи;
вкладені блоки показують дерево компонентів;
колір відображає витрати часу;
вибір компонента відкриває деталі саме цього компонента.
Компонент, який займає багато місця, не обов’язково є проблемою. Він може містити багато дочірніх компонентів, які й створюють основну частину витрат.
Тому аналізуйте дерево зверху вниз:
знайдіть повільний commit;
визначте великий або яскравий блок;
виберіть його;
перевірте, чи витрачав час сам компонент, чи його дочірні компоненти;
повторіть аналіз для дочірніх компонентів.
Представлення Ranked сортує компоненти за тривалістю рендерингу.
Воно зручне, коли потрібно швидко знайти найдорожчі компоненти в обраному commit.
Наприклад, якщо список із 500 рядків оновлюється після кожного натискання клавіші, у Ranked view можуть опинитися:
компонент усього списку;
компонент окремого рядка;
компонент форматування даних;
компонент, який виконує дороге обчислення.
Це допомагає перейти від загального спостереження «пошук повільний» до конкретного висновку: «кожен рядок списку перерендерюється під час введення».
Розглянемо застосунок зі списком товарів. Під час введення пошукового запиту він фільтрує список і відображає результати.
import { useMemo, useState } from 'react';
const products = Array.from({ length: 300 }, (_, index) => ({
id: index + 1,
name: `Товар ${index + 1}`,
price: (index + 1) * 10,
}));
function ProductList({ items }) {
return (
<ul>
{items.map((product) => (
<ProductItem key={product.id} product={product} />
))}
</ul>
);
}
function ProductItem({ product }) {
let total = 0;
// Імітуємо дороге обчислення для профілювання
for (let index = 0; index < 20000; index += 1) {
total += Math.sqrt(index);
}
return (
<li>
{product.name}: {product.price} грн
<small> ({Math.round(total)})</small>
</li>
);
}
export default function App() {
const [query, setQuery] = useState('');
const filteredProducts = useMemo(() => {
const normalizedQuery = query.toLowerCase();
return products.filter((product) =>
product.name.toLowerCase().includes(normalizedQuery),
);
}, [query]);
return (
<main>
<h1>Пошук товарів</h1>
<label>
Пошуковий запит
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Наприклад, Товар 12"
/>
</label>
<p>Знайдено: {filteredProducts.length}</p>
<ProductList items={filteredProducts} />
</main>
);
}Запустіть застосунок, відкрийте Profiler і виконайте такі кроки:
Почніть запис.
Введіть кілька символів у поле.
Зупиніть запис.
Виберіть commit, створений під час введення.
Відкрийте Flamegraph або Ranked view.
Знайдіть ProductItem та ProductList.
Порівняйте їхню тривалість рендерингу.
У цьому прикладі кожна зміна query призводить до нового масиву filteredProducts, після чого React рендерить результати пошуку. Додаткове обчислення в ProductItem робить витрати помітнішими під час профілювання.
У деталях профілю можуть відображатися два важливі значення.
Actual duration — фактичний час, витрачений на поточний рендеринг компонента та його піддерева.
Це значення може зменшитися після оптимізації, наприклад після:
мемоізації компонента;
стабілізації props;
зменшення кількості компонентів, які оновлюються;
спрощення дорогого обчислення.
Base duration — оцінка часу, який знадобився б для повторного повного рендерингу компонента та його піддерева без оптимізацій React.
Це значення корисне як орієнтир складності дерева. Якщо actualDuration значно менше за baseDuration, частина роботи вже була оптимізована або пропущена.
Ці значення не слід сприймати як універсальний час роботи застосунку. На них впливають:
потужність комп’ютера;
браузер;
режим розробки;
поточне навантаження системи;
розмір даних;
інші відкриті вкладки та розширення.
Порівнюйте результати в однакових умовах і звертайте увагу насамперед на тенденцію.
Profiler допомагає побачити, які компоненти оновилися під час конкретного commit.
Сам факт повторного рендерингу не означає помилку. React може повторно викликати компонент, навіть якщо результат інтерфейсу фактично не змінився. Проблема виникає тоді, коли такі оновлення:
відбуваються дуже часто;
запускають дорогі обчислення;
оновлюють велике дерево компонентів;
викликаються через зміну даних, які не впливають на компонент.
Під час аналізу компонента поставте такі запитання:
Чи повинен він оновлюватися після цієї дії?
Чи змінилися його props?
Чи змінився стан самого компонента?
Чи змінився контекст, який він використовує?
Чи створюються нові об’єкти або функції під час кожного рендера батьківського компонента?
Чи виконує компонент дорогу роботу без потреби?
Profiler потрібно використовувати не лише для пошуку проблеми, а й для перевірки результату оптимізації.
Рекомендований процес:
Запишіть поведінку до змін.
Зафіксуйте конкретну дію та обраний commit.
Запишіть actual duration проблемного компонента.
Зробіть одну оптимізацію.
Повторіть ту саму дію.
Порівняйте результати.
Переконайтеся, що поведінка застосунку не змінилася.
Не варто застосовувати memo, useMemo або useCallback лише тому, що компонент рендериться повторно. Спочатку знайдіть вимірювану проблему, а потім перевірте, чи справді оптимізація її зменшила.
У вибраному компоненті React DevTools може показувати, чому він оновився. Залежно від версії React DevTools та режиму роботи це може бути інформація про:
зміну props;
зміну стану;
зміну контексту;
батьківський рендеринг.
Ця інформація допомагає пов’язати спостереження з кодом.
Наприклад, якщо ProductItem оновлюється через батьківський компонент, перевірте:
чи змінилися product;
чи справді змінився елемент списку;
чи використовуються стабільні значення key;
чи не створюються непотрібні нові значення для дочірнього компонента.
Profiler показує вимірювання роботи React, але не пояснює автоматично, яку саме зміну потрібно внести в код.
Під час аналізу враховуйте:
один повільний commit може бути нормальним, якщо він відбувається рідко;
короткий commit, який запускається після кожного символу, може бути важливішим;
ширина блоку на Flamegraph показує не обов’язково час самого компонента;
компонент може бути повільним через дочірні компоненти;
результати в режимі розробки можуть відрізнятися від результатів у production;
вимірювання в браузері не є абсолютно стабільними.
Орієнтуйтеся не на довільне число мілісекунд, а на користувацький сценарій і повторюваність проблеми.
Визначте дію, яка здається повільною.
Запишіть цю дію через Profiler.
Виберіть найдовший або найпроблемніший commit.
Перегляньте Flamegraph.
Перейдіть до Ranked view для сортування компонентів за часом.
Виберіть повільний компонент.
З’ясуйте причину його оновлення.
Перевірте в коді обчислення, стан і props.
Внесіть одну зміну.
Повторіть вимірювання.
Такий підхід кращий за оптимізацію навмання, оскільки кожна зміна має перевірений результат.
Якщо записувати все від відкриття сторінки до завершення роботи, профіль буде складно аналізувати.
Краще записувати короткі сценарії: одну дію або невелику послідовність пов’язаних дій.
Компонент може бути викликаний повторно, але React не обов’язково змінить DOM. Повторний рендеринг і фактична зміна елементів сторінки — різні поняття.
Колір Flamegraph допомагає швидко зорієнтуватися, але не є достатнім доказом проблеми. Завжди перевіряйте тривалість commit і конкретного компонента.
Додавання мемоізації до всіх компонентів може ускладнити код і не дати користі. Спочатку зробіть вимірювання, потім оптимізуйте, після чого виміряйте повторно.
Результати введення одного символу та завантаження великого списку не можна безпосередньо порівнювати. Використовуйте однакові дані й однакову послідовність дій.
Показники режиму розробки не слід безпосередньо прирівнювати до показників production. Розробницькі перевірки та додаткові повідомлення можуть впливати на швидкість.
React DevTools Profiler вимірює роботу React під час оновлень інтерфейсу.
Окреме оновлення React називається commit.
Flamegraph показує дерево компонентів і їхній внесок у рендеринг.
Ranked view допомагає швидко знайти найдорожчі компоненти.
actualDuration показує фактичну тривалість поточного рендерингу.
baseDuration оцінює повну вартість рендерингу дерева без оптимізацій.
Повторний рендеринг не завжди є проблемою — важливі його частота та вартість.
Надійний процес оптимізації складається з вимірювання до змін, однієї оптимізації та повторного вимірювання після змін.