[Перевод] Профилирование производительности React-приложений
Сегодня поговорим об измерении производительности рендеринга React-компонентов с использованием API React Profiler. Ещё мы будем оценивать взаимодействия с компонентом, применяя новый экспериментальный API Interaction Tracing. Кроме того, мы воспользуемся API User Timing для проведения собственных измерений.
В качестве площадки для экспериментов воспользуемся приложением React Movies Queue.
Приложение React Movies Queue
API React Profiler
API React Profiler предназначен для оценки скорости работы рендеринга и помогает выявлять узкие места производительности приложений.
import React, { Fragment, unstable_Profiler as Profiler} from "react";
Компонент Profiler
принимает коллбэк onRender
в виде свойства. Он вызывается каждый раз, когда компонент в профилируемом дереве фиксирует обновление.
const Movies = ({ movies, addToQueue }) => (
Давайте, в тестовых целях, попробуем измерить время, необходимое на рендеринг частей компонента Movies
. Вот как это выглядит.
Приложение React Movies Queue и исследование Movies с помощью инструментов разработчика React
Коллбэк onRender
принимает параметры, которые описывают то, что рендерится, и время, необходимое на рендеринг. Сюда входит следующее:
id
: Свойствоid
из дерева компонентаProfiler
, для которого было зафиксировано изменение.phase
: илиmount
(если дерево было смонтировано), илиupdate
(если дерево было повторно отрендерено).actualDuration
: время, затраченное на рендеринг зафиксированного обновления.baseDuration
: предполагаемое время рендеринга всего поддерева без кеширования.startTime
: время, когда React начал рендерить это обновление.commitTime
: время, когда когда React зафиксировал это обновление.interactions
: множество «взаимодействий» для данного обновления.
const callback = (id, phase, actualTime, baseTime, startTime, commitTime) => {
console.log(`${id}'s ${phase} phase:`);
console.log(`Actual time: ${actualTime}`);
console.log(`Base time: ${baseTime}`);
console.log(`Start time: ${startTime}`);
console.log(`Commit time: ${commitTime}`);
}
Загрузим страницу и перейдём в консоль инструментов разработчика Chrome. Там мы должны увидеть следующее.
Результаты профилирования в инструментах разработчика
Мы, кроме того, можем открыть инструменты разработчика React, перейти на закладку Profiler
и визуализировать сведения о времени рендеринга компонентов. Ниже показана визуализация этого времени в виде flame-графика.
Работа с результатами профилирования в инструментах разработчика React
Мне, кроме того, нравится использовать тут режим просмотра Ranked
, где приводится упорядоченное представление данных. В результате компоненты, на рендеринг которых уходит больше всего времени, оказываются в верхней части списка.
Просмотр результатов профилирования в режиме Ranked
Кроме того, для проведения измерений в разных частях приложения можно воспользоваться несколькими компонентами Profiler
:
import React, { Fragment, unstable_Profiler as Profiler} from "react";
render(
);
А как проанализировать взаимодействия пользователей с компонентами?
API Interaction Tracing
Хорошо бы иметь возможность отслеживать взаимодействия пользователя с интерфейсом приложения (вроде щелчков по элементам). Это позволит находить ответы на интересные вопросы вроде такого: «Сколько времени понадобилось на обновление DOM после щелчка по этой кнопке?». К нашему счастью, в React есть экспериментальная поддержка анализа взаимодействий пользователя с приложением с использованием API Interaction Tracing из нового пакета scheduler. Документацию по нему можно почитать здесь.
Сведения о взаимодействиях пользователя и приложения снабжают описаниями (например — «Пользователь щёлкнул по кнопке Добавить в корзину») и отметками времени. Кроме того, при настройке анализа взаимодействий используются коллбэки, в которых выполняются действия, соответствующие тому или иному взаимодействию.
В нашем приложении есть кнопка Add Movie To Queue
, на которой выводится значок +
. Она служит для добавления фильмов в очередь просмотра.
Кнопка для добавления фильма в очередь просмотра
Вот пример кода, отслеживающего обновления состояния для этого взаимодействия пользователя с приложением:
import { unstable_Profiler as Profiler } from "react";
import { render } from "react-dom";
import { unstable_trace as trace } from "scheduler/tracing";
class MyComponent extends Component {
addMovieButtonClick = event => {
trace("Add To Movies Queue click", performance.now(), () => {
this.setState({ itemAddedToQueue: true });
});
};
}
Мы можем записать сведения об этом взаимодействии и узнать о его длительности, обратившись к инструментам разработчика React.
Анализ взаимодействия пользователя с элементом приложения
С помощью API Interaction Tracing можно, кроме того, собрать сведения о первом рендеринге компонента:
import { unstable_trace as trace } from "scheduler/tracing";
trace("initial render", performance.now(), () => {
ReactDom.render( , document.getElementById("app"));
});
Анализ первого рендеринга компонента
Автор API приводит и другие примеры его использования. Например, иллюстрирующие профилирование асинхронных задач.
Puppeteer
Для автоматизации тестирования взаимодействия пользователя с элементами приложения интересным может показаться применение Puppeteer. Это — Node.js-библиотека, которая даёт доступ к высокоуровневому API, предназначенному для управления браузером Chrome без пользовательского интерфейса с использованием протокола DevTools.
При использовании Puppeteer в распоряжении разработчика оказываются вспомогательные методы tracing.start()
и tracing.stop()
, предназначенные для сбора показателей производительности DevTools. Ниже показан пример использования этих механизмов для сбора данных о том, что происходит при щелчке по интересующей нас кнопке.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const navigationPromise = page.waitForNavigation();
await page.goto('https://react-movies-queue.glitch.me/')
await page.setViewport({ width: 1276, height: 689 });
await navigationPromise;
const addMovieToQueueBtn = 'li:nth-child(3) > .card > .card__info > div > .button';
await page.waitForSelector(addMovieToQueueBtn);
// Запуск профилирования...
await page.tracing.start({ path: 'profile.json' });
// Щелчок по кнопке
await page.click(addMovieToQueueBtn);
// Остановка профилирования
await page.tracing.stop();
await browser.close();
})()
Теперь, загрузив файл profile.json
в панель Performance
инструментов разработчика, мы можем видеть то, вызов каких функций инициировало нажатие на кнопку.
Анализ последствий нажатия на кнопку
Если вам интересна тема анализа производительности компонентов — взгляните на этот материал.
API User Timing
API User Timing позволяет разработчику создавать собственные метрики производительности, используя высокоточные отметки времени. Метод window.performance.mark()
создаёт отметку времени, которой назначается имя. Метод window.performance.measure()
позволяет узнать время, прошедшее между двумя измерениями.
// Запись времени перед началом задачи
performance.mark('Movies:updateStart');
// Решение задачи
// Запись времени после окончания задачи
performance.mark('Movies:updateEnd');
// Нахождение разницы между временем начала и окончания задачи
performance.measure('moviesRender', 'Movies:updateStart', 'Movies:updateEnd');
Профилируя React-приложением с использованием закладки Performance
инструментов разработчика Chrome, можно найти раздел Timing
, заполненный временными показателями, касающимися React-компонентов. React, во время рендеринга, умеет публиковать эти сведения с использованием API User Timing.
Закладка Performance инструментов разработчика Chrome
Обратите внимание на то, что из DEV-сборок React убирают API User Timings, заменяя его на API React Profiler, который предоставляет более точные отметки времени. Возможно, в будущем поддержку этого API вернут, сделав это для тех браузеров, которые поддерживают спецификацию User Timing Level 3.
В интернете вам могут попасться React-сайты, которые используют API User Timing для определения собственных метрик. Сюда, например, входит метрика Reddit Time to first post title visible
и метрика Spotify Time to playback ready
.
Особые метрики, используемые на React-сайтах
Показатели метрик, созданных средствами API User Timing, удобно выводятся в панели Lighthouse инструментов разработчика Chrome.
Метрики в панели Lighthouse
Например, в состав свежих версий Next.js включены пользовательские метрики и механизмы измерения множества различных событий. В том числе — следующих:
Next.js-hydration
: время, необходимое для приведения заранее отрендеренной разметки в рабочее состояние.Next.js-nav-to-render
: время от начала навигации до начала рендеринга.
Все эти измерения выводятся в области Timings
.
Анализ метрик Next.js
Инструменты разработчика и Lighthouse
Напоминаю, что Lighthouse и панель Performance
инструментов разработчика Chrome можно использовать для глубокого анализа процесса загрузки и производительности React-приложений. Здесь можно найти метрики, особенно сильно влияющие на восприятие страниц пользователями.
Анализ производительности страницы
Тем, кто работает с React, может понравиться то, что в их распоряжении окажутся новые метрики — вроде показателя Total Blocking Time (TBT), который даёт сведения о том, сколько времени страница пребывает в неинтерактивном режиме до того момента, когда она сможет надёжно работать в интерактивном режиме (Time to Interactive). Вот — показатели TBT («до» и «после») для приложения, в котором применяется экспериментальный конкурентный режим, использование которого помогает приложению адаптироваться к особенностям среды, в которой оно выполняется.
Изменение TBT
Эти инструменты полезны при анализе узких мест производительности приложения, таких, как задачи, на выполнение которых уходит много времени, откладывающие переход приложения в интерактивный режим. Например, это может относиться к анализу скорости реакции приложения на нажатия кнопок.
Анализ приложения
Lighthouse, кроме того, даёт React-разработчикам массу специфических советов по множеству вопросов. Ниже показан результат анализа в Lighthouse 6.0. Здесь открыт раздел Remove unused JavaScript, в котором сообщается о неиспользуемом JavaScript-коде, загружаемом в приложение, который может быть импортирован с использованием React.lazy()
.
Анализ приложения в Lighthouse
Приложения всегда полезно проверять на аппаратном обеспечении, которое, вероятнее всего, имеется у его конечных пользователей. Я часто полагаюсь в подобных вопросах на Webpagetest и на данные RUM и CrUX, которые позволяют мне получать более полные сведения о производительности приложений.
Уважаемые читатели! Как вы исследуете производительность своих React-приложений?