INP — это показатель веб-производительности, который измеряет скорость реагирования пользовательского интерфейса — насколько быстро веб-сайт реагирует на взаимодействия с пользователем, такие как клики или нажатия клавиш.
В частности, он измеряет, сколько времени проходит между взаимодействием с пользователем, например щелчком мыши или нажатием клавиши, и следующим моментом, когда пользователь увидит визуальное обновление на странице.
Как измеряется взаимодействие с Next Paint
INP измеряет, сколько времени проходит между пользовательским вводом, например щелчками и нажатиями клавиш, и следующим обновлением пользовательского интерфейса. Эта задержка состоит из трех компонентов :
- Задержка ввода : ожидание фоновых задач на странице, которые препятствуют запуску обработчика событий.
- Время обработки : запуск обработчиков событий в JavaScript
- Задержка презентации : перерасчет макета страницы и рисование содержимого страницы.
Какое значение взаимодействия с Next Paint является хорошим
Хороший INP составляет менее 200 миллисекунд . INP более 500 миллисекунд – это плохо.
Взаимодействие с Next Paint станет одним из показателей Core Web Vitals в марте 2024 года и, следовательно, станет фактором ранжирования Google .
Как оптимизировать взаимодействие с Next
Лучший способ уменьшить INP — свести к минимуму объем обработки ЦП, выполняемой на вашей странице. Вы также можете выполнять большую часть своего кода асинхронно, чтобы пользователь получал немедленное обновление пользовательского интерфейса, даже если какая-то работа еще продолжается.
Оптимизация INP включает в себя профилирование кода вашего собственного веб-сайта, а также проверку сторонних скриптов, которые могут сделать ваш веб-сайт менее отзывчивым.
Профилировщик производительности DevTools — отличный инструмент, позволяющий узнать, что происходит в основном потоке страницы и как его можно оптимизировать.
Определите типичные действия пользователя на вашей странице и создайте запись активности процессора на странице, которая происходит во время взаимодействия. Затем вы можете исследовать взаимодействия с наибольшей задержкой.
Чтобы уменьшить компонент INP «Задержка ввода», уменьшите и разделите фоновую активность ЦП в основном потоке .
При просмотре лабораторных данных вы можете посмотреть показатель «Общее время блокировки» , чтобы узнать, есть ли фоновая активность, которая может блокировать взаимодействие с пользователем.
Если за фоновые задачи отвечает сторонний код, проверьте, можно ли настроить код для уменьшения объема работы или можно ли загружать сторонний скрипт только при необходимости.
Вам необходимо выяснить, где браузер проводит большую часть своего времени, и оптимизировать эти части.
В приложении React вы можете избежать ненужной повторной визуализации ваших компонентов , чтобы ускорить работу вашего веб-сайта.
Используйте React.memo или
Когда компонент перерисовывается, React также по умолчанию перерисовывает дочерние компоненты.
Вот простое приложение с двумя Counter
компонентами и кнопкой, которая увеличивает один из них.
function App() {
const [counterA, setCounterA] = React.useState(0);
const [counterB, setCounterB] = React.useState(0);
return (
<div>
<Counter name="A" value={counterA} />
<Counter name="B" value={counterB} />
<button
onClick={() => {
console.log("Click button");
setCounterA(counterA + 1);
}}
>
Increment counter A
</button>
</div>
);
}
function Counter({ name, value }) {
console.log(`Rendering counter ${name}`);
return (
<div>
{name}: {value}
</div>
);
}
Сейчас оба Counter
компонента визуализируются при нажатии кнопки, хотя изменился только счетчик A.
Click button
Rendering counter A
Rendering counter B
Компонент более React.memo
высокого порядка (HOC) может гарантировать повторную визуализацию компонента только при изменении его свойств.
const Counter = React.memo(function Counter({ name, value }) {
console.log(`Rendering counter ${name}`);
return (
<div>
{name}: {value}
</div>
);
});
Теперь перерисовывается только счетчик A, поскольку его value
свойство изменено с 0
на 1
.
Click button
Rendering counter A
Если вы используете компоненты на основе классов вместо функциональных компонентов, измените extends React.Component
на extends React.PureComponent
, чтобы получить тот же эффект.
Убедитесь, что значения свойств не меняются
Предотвратить рендеринг в нашем примере было довольно легко. Но на практике это сложнее, так как легко внести непреднамеренные изменения в реквизит.
Давайте добавим в Counter
компонент кнопку Increment.
const Counter = React.memo(function Counter({ name, value, onClickIncrement }) {
console.log(`Rendering counter ${name}`);
return (
<div>
{name}: {value} <button onClick={onClickIncrement}>Increment</button>
</div>
);
});
Теперь компонент App
передает onClickIncrement
свойство каждому Counter
.
<Counter
name="A"
value={counterA}
onClickIncrement={() => setCounterA(counterA + 1)}
/>
Если вы увеличиваете счетчик A, оба счетчика отображаются повторно.
Rendering counter A
Rendering counter B
Почему? Потому что значение свойства onClickIncrement
меняется каждый раз, когда приложение перерисовывается. Каждая функция представляет собой отдельный объект JavaScript, поэтому React видит изменение свойства и обязательно обновляет файл Counter
.
Это имеет смысл, поскольку onClickIncrement
функция зависит от counterA
значения из родительской области видимости. Если бы одна и та же функция передавалась каждый Counter
раз, приращение перестанет работать, поскольку начальное значение счетчика никогда не обновится. Значение счетчика будет устанавливаться 0 + 1 = 1
каждый раз.
Проблема в том, что onClickIncrement
функция меняется каждый раз, даже если значение счетчика, на которое она ссылается, не изменилось.
Мы можем использовать useCallback
крючок, чтобы это исправить. useCallback
запоминает переданную функцию, так что новая функция возвращается только при изменении одной из зависимостей перехватчика.
В данном случае зависимостью является counterA
состояние. Когда это изменится, onClickIncrement
функция должна обновиться, чтобы мы не использовали устаревшее состояние позже.
<Counter
name="A"
value={counterA}
onClickIncrement={React.useCallback(() => setCounterA(counterA + 1), [
counterA,
])}
/>
Если мы сейчас увеличим счетчик A, перерисовывается только счетчик A.
Rendering counter A
Если вы используете компоненты на основе классов, добавьте в класс методы и используйте функцию bind
в конструкторе, чтобы обеспечить доступ к экземпляру компонента.
constructor(props) {
super(props)
this.onClickIncrementA = this.onClickIncrementA.bind(this)
}
(Вы не можете вызвать bind
функцию рендеринга, так как она возвращает новый объект функции и приведет к повторному рендерингу.)
Передача объектов в качестве реквизита
Непреднамеренный повторный рендеринг происходит не только с функциями, но и с литералами объектов.
function App() {
return <Heading style={{ color: "blue" }}>Hello world</Heading>
}
Каждый раз, когда App
компонент визуализирует, создается новый объект стиля, что приводит Heading
к обновлению запомненного компонента.
К счастью, в этом случае объект стиля всегда один и тот же, поэтому мы можем просто создать его один раз вне компонента, App
а затем повторно использовать для каждого рендеринга.
const headingStyle = { color: "blue" }
function App() {
return <Heading style={headingStyle}>Hello world</Heading>
}
А что, если стиль рассчитывается динамически? В этом случае вы можете использовать useMemo
хук, чтобы ограничить время обновления объекта.
function App({ count }) {
const headingStyle = React.useMemo(
() => ({
color: count < 10 ? "blue" : "red",
}),
[count < 10]
);
return <Heading style={headingStyle}>Hello world</Heading>
}
Обратите внимание, что зависимостью от ловушки является не простое значение count
, а count < 10
условие. Таким образом, если счетчик изменится, заголовок будет отображаться повторно только в том случае, если изменится и цвет.
Мы получаем те же проблемы с идентификацией объекта и непреднамеренным повторным рендерингом, если передаваемые нами дочерние элементы представляют собой нечто большее, чем просто простая строка.
<Heading>
<strong>Hello world</strong>
</Heading>
Однако применяются те же решения. Если дочерние элементы статичны, переместите их из функции. Если они зависят от состояния, используйте useMemo
.
function App({}) {
const content = React.useMemo(() => <strong>Hello world ({count}</strong>, [
count,
]);
return (
<>
<Heading>{content}</Heading>
</>
);
}
Использование ключей, чтобы избежать повторного рендеринга
Ключевые реквизиты позволяют React идентифицировать элементы при рендеринге. Они чаще всего используются при рендеринге списка элементов.
Если каждый элемент списка имеет согласованный ключ, React может избежать повторной отрисовки компонентов даже при добавлении или удалении элементов списка.
function App() {
console.log("Render App");
const [items, setItems] = React.useState([{ name: "A" }, { name: "B" }]);
return (
<div>
{items.map((item) => (
<ListItem item={item} />
))}
<button onClick={() => setItems(items.slice().reverse())}>Reverse</button>
</div>
);
}
const ListItem = React.memo(function ListItem({ item }) {
console.log(`Render ${item.name}`);
return <div>{item.name}</div>;
});
Без ключа <ListItem>
мы получаем Warning: Each child in a list should have a unique "key" prop
сообщение.
Это вывод журнала при нажатии кнопки «Реверс».
=> Reverse
Render app
Render B
Render A
Вместо перемещения элементов React обновляет их оба и передает новый item
реквизит.
Добавление уникального ключа к каждому элементу списка решает проблему.
<ListItem item={item} key={item.name} />
React теперь может правильно распознать, что элементы не изменились, и просто перемещает существующие элементы.
Что такое хороший ключ
Ключи должны быть уникальными, и никакие два элемента в списке не должны иметь одинаковый ключ. По этой причине ключ item.name
, который мы использовали выше, не идеален, поскольку несколько элементов списка могут иметь одно и то же имя. По возможности назначайте уникальный идентификатор каждому элементу списка — часто вы получаете его из внутренней базы данных.
Ключи также должны быть стабильными. Если вы используете Math.random()
ключ, он будет меняться каждый раз, что приведет к повторному монтированию и повторной визуализации компонента.
Для статических списков, в которых элементы не добавляются и не удаляются, также можно использовать индекс массива.
Ключи на фрагментах
Вы не можете добавлять ключи к фрагментам, используя короткий синтаксис ( <>
), но это работает, если вы используете полное имя:
<React.Fragment key={item.name}>
</React.Fragment>
Избегайте изменений в древовидной структуре
Дочерние компоненты будут перемонтированы, если изменится окружающая структура DOM. Например, это приложение добавляет контейнер вокруг списка. В более реалистичном приложении вы можете поместить элементы в разные группы в зависимости от настроек.
function App() {
console.log("Render App");
const [items, setItems] = React.useState([{ name: "A" }, { name: "B" }]);
const [showContainer, setShowContainer] = React.useState(false);
const els = items.map((item) => <ListItem item={item} key={item.name} />);
return (
<div>
{showContainer > 0 ? <div>{els}</div> : els}
<button onClick={() => setShowContainer(!showContainer)}>
Toggle container
</button>
</div>
);
}
const ListItem = React.memo(function ListItem({ item }) {
console.log(`Render ${item.name}`);
return <div>{item.name}</div>;
});
При добавлении родительского компонента все существующие элементы списка размонтируются и создаются новые экземпляры компонента. Инструменты разработчика React показывают, что это первый рендеринг компонента.
По возможности сохраняйте структуру DOM прежней. Например, если вам нужно отобразить разделители между группами внутри списка, вставьте разделители между элементами списка вместо добавления обертки div к каждой группе.
Отслеживайте производительность вашего приложения
DebugBear может отслеживать время загрузки и активность процессора вашего веб-сайта с течением времени. Просто введите свой URL-адрес, чтобы начать.
Если большая часть активности процессора приходится на работу с компоновкой, проверьте, можете ли вы уменьшить количество ретрансляций .
Если вам необходимо выполнить интенсивную обработку JavaScript в ответ на ввод пользователя, рассмотрите возможность обновления пользовательского интерфейса перед запуском задачи ЦП. Например, вы:
- покажите счетчик, а затем выполните работу в
setTimeout
обратном вызове - выполнять ресурсоемкую работу в отдельном потоке с помощью веб-воркера
Избегайте встроенных
Такие методы JavaScript, как alert
— это простой способ показать сообщение пользователю или попросить его подтвердить действие. Однако они выполняются синхронно и блокируют основной поток страницы, а это означает, что все время, пока они видны, учитывается в общей задержке взаимодействия.
Планируется пересмотреть это перед полным развертыванием INP в качестве показателя Core Web Vitals.
Величина задержки представления будет зависеть от сложности страницы и ее объема.
Если рендеринг содержимого страницы происходит медленно, рассмотрите возможность сначала показывать только важный контент «над сгибом», чтобы быстрее доставить следующий кадр.
Чем взаимодействие со следующей отрисовкой отличается от задержки первого ввода
Между FID и INP есть два различия : FID измеряет только начальную задержку обработки, тогда как INP измеряет полный промежуток времени между вводом пользователя и обновлением пользовательского интерфейса. FID также учитывает только первое взаимодействие пользователя со страницей, тогда как INP учитывает (примерно) наихудшую задержку.
Первая задержка ввода измеряет, сколько времени требуется браузеру, чтобы начать обработку пользовательского ввода. Фактическое время, потраченное на реакцию на событие или обновление пользовательского интерфейса, не учитывается.
Как следует из названия, FID учитывает только первый раз, когда пользователь взаимодействует со страницей. Особенно для страниц, которые остаются открытыми в течение длительного времени, таких как одностраничные приложения, это первое взаимодействие может не отражать общее впечатление пользователя.
Взаимодействие с Next Paint обычно измеряет наихудшую задержку ввода на странице. Однако, если имеется много отклонений пользовательского опыта, они будут игнорироваться, и Google будет измерять 98-й процентиль задержки взаимодействия. Таким образом, если INP на странице составляет 250 миллисекунд, 2% пользовательских взаимодействий имели задержку более 250 миллисекунд.
Является ли «Взаимодействие с следующей картинкой» показателем Core Web Vitals
INP станет одним из основных показателей Google Web Vitals в марте 2024 года. На этом этапе он заменит задержку первого ввода.
Если вы станете метрикой Core Web Vitals, то плохое взаимодействие с Next Paint может повлиять на ваш рейтинг в поиске Google .
Почему INP является полевой метрикой
Для измерения показателя «Взаимодействие с следующей отрисовкой» требуется ввод данных пользователем, поэтому он обычно доступен в полевых данных, собранных от реальных пользователей .
Google собирает данные реальных пользователей в INP в рамках отчета об опыте пользователей Chrome (CrUX) .
Вы можете создать сценарий взаимодействия с пользователем в лабораторной среде для сбора лабораторных данных для INP. Имейте в виду, что каждое взаимодействие со страницей может иметь разные характеристики производительности в зависимости от того, с каким элементом пользовательского интерфейса взаимодействует пользователь и когда это взаимодействие происходит.
Отладчик INP позволяет выявлять элементы страницы с плохой реакцией в лабораторной среде. Инструмент автоматически взаимодействует с элементами страницы и измеряет задержку ответа.
Измеряет ли INP самый медленный отклик на странице
Когда пользователь посещает страницу, он может нажать на несколько разных элементов пользовательского интерфейса. Если они заполняют форму, они нажимают множество клавиш на клавиатуре. Как INP объясняет это?
INP сообщит о самых медленных 2% ответов пользовательского интерфейса. Обычно это означает, что будет сообщаться о наихудшей задержке, но если страница получает много взаимодействий, будет сообщаться только об одной из самых медленных.
Например, если странице требуется 50 миллисекунд для ответа на 100 взаимодействий, а затем происходит одно взаимодействие с задержкой в 300 миллисекунд, то INP будет сообщен как 50 миллисекунд.
Однако если есть три задержки по 300 миллисекунд, то 98-й процентиль составит 300 миллисекунд, и об этом будет сообщено как INP.
Какие взаимодействия с пользователем учитывает INP
Для INP учитываются следующие взаимодействия:
- Щелчки мыши
- Касания (на сенсорном экране)
- Нажатия клавиш
Следующие взаимодействия не учитываются:
- зависание
- Прокрутка
Изменения в
Google постоянно совершенствует определение показателей Core Web Vitals. Если вы видите необъяснимые изменения в своих показателях, это может быть связано с изменением способа измерения INP. Ознакомьтесь с журналом изменений INP , чтобы узнать, какие изменения были внесены в разные версии Chrome.
Как измерить взаимодействие со следующей
Для измерения взаимодействия со следующей отрисовкой можно использовать ряд инструментов, например:
- Тест скорости сайта DebugBear
- Статистика Google PageSpeed
- Расширение Site Speed для Chrome
- Отладчик INP
- Консоль поиска Google
- Библиотека веб-показателей
Измерьте взаимодействие с следующей отрисовкой с помощью
Бесплатный тест скорости веб-сайта от DebugBear может показать вам, каково взаимодействие с Next Paint для реальных пользователей на вашем веб-сайте.