useDeferredValue¶
useDeferredValue
- это хук React, который позволяет отложить обновление части пользовательского интерфейса.
1 |
|
Описание¶
useDeferredValue(value)
¶
Вызовите useDeferredValue
на верхнем уровне вашего компонента, чтобы получить отложенную версию этого значения.
1 2 3 4 5 6 7 |
|
Параметры¶
value
: Значение, которое вы хотите отложить. Оно может иметь любой тип.
Возвращает¶
Во время первоначального рендеринга возвращаемое отложенное значение будет таким же, как и предоставленное вами значение. Во время обновления React сначала попытается выполнить повторный рендеринг со старым значением (поэтому вернет старое значение), а затем попытается выполнить повторный рендеринг в фоновом режиме с новым значением (поэтому вернет обновленное значение).
Предупреждения¶
- Значения, которые вы передаете в
useDeferredValue
, должны быть либо примитивными значениями (такими как строки и числа), либо объектами, созданными вне рендеринга. Если вы создадите новый объект во время рендеринга и сразу передадите его вuseDeferredValue
, он будет отличаться при каждом рендеринге, что приведет к ненужным повторным рендерам фона. - Когда
useDeferredValue
получает другое значение (по сравнению сObject.is
), в дополнение к текущему рендеру (когда он все еще использует предыдущее значение), он планирует повторный рендер в фоновом режиме с новым значением. Фоновый рендеринг можно прервать: если произойдет очередное обновлениеvalue
, React перезапустит фоновый рендеринг с нуля. Например, если пользователь набирает текст в поле ввода быстрее, чем график, получающий отложенное значение, успевает отрисоваться, график отрисуется только после того, как пользователь перестанет набирать текст. useDeferredValue
интегрировано с<Suspense>
. Если фоновое обновление, вызванное новым значением, приостанавливает работу пользовательского интерфейса, пользователь не увидит откат. Он будет видеть старое отложенное значение до тех пор, пока данные не загрузятся.- Функция
useDeferredValue
сама по себе не предотвращает дополнительные сетевые запросы. - Не существует фиксированной задержки, вызванной самим
useDeferredValue
. Как только React завершит первоначальный рендеринг, React немедленно начнет работу над фоновым рендерингом с новым отложенным значением. Любые обновления, вызванные событиями (например, вводом текста), будут прерывать фоновый рендеринг и получат приоритет над ним. - Фоновый рендеринг, вызванный
useDeferredValue
, не запускает эффекты до тех пор, пока они не будут зафиксированы на экране. Если фоновый рендеринг приостановлен, его Эффекты будут запущены после загрузки данных и обновления пользовательского интерфейса.
Использование¶
Показ устаревшего содержимого во время загрузки свежего¶
Вызовите useDeferredValue
на верхнем уровне вашего компонента, чтобы отложить обновление некоторой части вашего пользовательского интерфейса.
1 2 3 4 5 6 7 |
|
Во время первоначального рендеринга отложенное значение будет таким же, как и предоставленное вами значение.
Во время обновлений отложенное значение будет "отставать" от последнего значения. В частности, React сначала выполнит рендеринг без обновления отложенного значения, а затем попытается выполнить рендеринг с вновь полученным значением в фоновом режиме.
Давайте рассмотрим пример, чтобы увидеть, когда это полезно.
Пример
Этот пример предполагает, что вы используете один из источников данных с поддержкой Suspense:
- Получение данных с помощью фреймворков с поддержкой Suspense, таких как Relay и Next.js.
- Ленивая загрузка кода компонента с помощью
lazy
Узнайте больше о Suspense и его ограничениях
В этом примере компонент SearchResults
приостанавливается на время получения результатов поиска. Попробуйте набрать "a"
, дождаться результатов, а затем изменить его на "ab"
. Результаты для "a"
будут заменены загружаемым возвратом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
Распространенным альтернативным шаблоном пользовательского интерфейса является отложенное обновление списка результатов и отображение предыдущих результатов до тех пор, пока не будут готовы новые результаты. Вызовите useDeferredValue
, чтобы передать отложенную версию запроса вниз:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Запрос query
будет обновлен немедленно, поэтому на входе будет отображаться новое значение. Однако deferredQuery
сохранит свое предыдущее значение до тех пор, пока данные не загрузятся, поэтому SearchResults
некоторое время будет показывать устаревшие результаты.
Введите "a"
в примере ниже, дождитесь загрузки результатов, а затем измените ввод на "ab"
. Обратите внимание, что вместо отката к приостановке теперь отображается список несвежих результатов, пока не загрузятся новые:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Как работает откладывание значения под капотом?
Можно представить, что это происходит в два этапа:
-
Сначала React выполняет рендеринг с новым
query
("ab"
), но со старымdeferredQuery
(все еще"a"
) ЗначениеdeferredQuery
, которое вы передаете в список результатов, является отложенным: оно "отстает" от значенияquery
. -
В фоновом режиме React пытается выполнить повторный рендеринг с обновленными значениями
query
иdeferredQuery
до"ab"
. Если этот повторный рендеринг завершится, React покажет его на экране. Однако, если он приостановится (результаты для"ab"
еще не загружены), React оставит эту попытку рендеринга и повторит ее снова после загрузки данных. Пользователь будет видеть отложенное значение до тех пор, пока данные не будут готовы.
Отложенный "фоновый" рендеринг можно прервать. Например, если вы снова введете данные в поле ввода, React покинет его и перезапустит с новым значением. React всегда будет использовать последнее предоставленное значение.
Обратите внимание, что при каждом нажатии клавиши все равно происходит запрос к сети. Здесь откладывается отображение результатов (пока они не будут готовы), а не сами сетевые запросы. Даже если пользователь продолжает набирать текст, ответы на каждое нажатие клавиши кэшируются, поэтому нажатие Backspace происходит мгновенно и не требует повторной выборки.
Указывает на то, что содержимое устарело¶
В приведенном выше примере нет указания на то, что список результатов последнего запроса все еще загружается. Это может сбить пользователя с толку, если новые результаты загружаются долго. Чтобы сделать более очевидным для пользователя, что список результатов не соответствует последнему запросу, можно добавить визуальную индикацию, когда отображается несвежий список результатов:
1 2 3 4 5 6 7 |
|
При таком изменении, как только вы начинаете вводить текст, список неактуальных результатов слегка затемняется, пока не загрузится новый список результатов. Вы также можете добавить CSS-переход для задержки затемнения, чтобы оно было постепенным, как в примере ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
Откладывание повторного рендеринга для части пользовательского интерфейса¶
Вы также можете применить useDeferredValue
в качестве оптимизации производительности. Это полезно, когда часть вашего пользовательского интерфейса медленно перерисовывается, нет простого способа оптимизировать ее, и вы хотите предотвратить блокировку остальной части пользовательского интерфейса.
Представьте, что у вас есть текстовое поле и компонент (например, график или длинный список), который перерисовывается при каждом нажатии клавиши:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Во-первых, оптимизируйте SlowList
для пропуска повторного рендеринга, когда его пропсы одинаковы. Для этого оберните это в memo
:
1 2 3 |
|
Однако это помогает только в том случае, если пропс SlowList
такой же, как и во время предыдущего рендеринга. Проблема, с которой вы столкнулись сейчас, заключается в том, что это медленно, когда они разные, и когда вам действительно нужно показать разные визуальные результаты.
Конкретно, основная проблема производительности заключается в том, что всякий раз, когда вы вводите данные на вход, SlowList
получает новые пропсы, и повторное отображение всего дерева приводит к тому, что ввод становится неаккуратным. В этом случае useDeferredValue
позволяет вам установить приоритет обновления ввода (которое должно быть быстрым) над обновлением списка результатов (которое может быть более медленным):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Это не делает повторное отображение SlowList
быстрее. Однако это сообщает React, что повторное отображение списка может быть деприоритизировано, чтобы не блокировать нажатия клавиш. Список будет "отставать" от ввода, а затем "догонять". Как и раньше, React будет пытаться обновить список как можно быстрее, но не будет блокировать ввод пользователем.
Разница между useDeferredValue и неоптимизированным повторным рендерингом¶
1. Отложенное повторное отображение списка¶
В этом примере каждый элемент компонента SlowList
искусственно замедляется, чтобы вы могли увидеть, как useDeferredValue
позволяет вам сохранить отзывчивость ввода. Наберите текст на клавиатуре и обратите внимание, что текст набирается быстро, в то время как список "отстает" от него.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
2. Неоптимизированный повторный рендеринг списка¶
В этом примере каждый элемент в компоненте SlowList
искусственно замедлен, но нет useDeferredValue
.
Обратите внимание на то, что ввод в поле ввода выглядит очень неаккуратно. Это потому, что без useDeferredValue
каждое нажатие клавиши заставляет весь список немедленно перерисовываться, не прерываясь.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Замечание
Эта оптимизация требует, чтобы SlowList
был обернут в memo
. Это происходит потому, что всякий раз, когда text
изменяется, React должен иметь возможность быстро перерисовать родительский компонент. Во время этой перерисовки deferredText
все еще имеет свое предыдущее значение, поэтому SlowList
может пропустить перерисовку (его пропсы не изменились). Без memo
, ему все равно пришлось бы перерисовываться, что сводит на нет смысл оптимизации.
Чем отсрочка значения отличается от дебаширования и дросселирования?
Есть две распространенные техники оптимизации, которые вы могли использовать раньше в этом сценарии:
- Дебаунсинг означает, что вы будете ждать, пока пользователь прекратит печатать (например, на секунду), прежде чем обновить список.
- Дросселирование означает, что вы будете обновлять список время от времени (например, не чаще одного раза в секунду).
Хотя эти методы полезны в некоторых случаях, useDeferredValue
лучше подходит для оптимизации рендеринга, поскольку он глубоко интегрирован в сам React и адаптируется к устройству пользователя.
В отличие от дебаунсинга или дросселирования, он не требует выбора какой-либо фиксированной задержки. Если устройство пользователя быстрое (например, мощный ноутбук), отложенный повторный рендеринг произойдет почти мгновенно и не будет заметен. Если устройство пользователя медленное, список будет "отставать" от ввода пропорционально скорости устройства.
Также, в отличие от дебаунсинга или дросселирования, отложенный рендеринг, выполняемый useDeferredValue
, по умолчанию прерывается. Это означает, что если React находится в середине рендеринга большого списка, но пользователь нажимает другую клавишу, React прервет этот рендеринг, обработает нажатие клавиши, а затем снова начнет рендеринг в фоновом режиме. В отличие от этого, дебаунсинг и дросселирование все еще вызывают неприятные ощущения, потому что они блокируют: они просто откладывают момент, когда рендеринг блокирует нажатие клавиши.
Если работа, которую вы оптимизируете, не происходит во время рендеринга, дебаунсинг и дросселирование все равно полезны. Например, они могут позволить вам выполнять меньше сетевых запросов. Вы также можете использовать эти методы вместе.
Источник — https://react.dev/reference/react/useDeferredValue