Spec-Zone.ru › React 18

useDeferredValue

useDeferredValue — это хук React, который позволяет отложить обновление части пользовательского интерфейса.

const deferredValue = useDeferredValue(value)
  • Ссылка
    • useDeferredValue(value, initialValue?)
  • Использование
    • Отображение устаревшего содержимого во время загрузки нового
    • Указание того, что содержимое устарело
    • Отсрочка перерисовки части пользовательского интерфейса

Ссылка

useDeferredValue(value, initialValue?)

Вызовите useDeferredValue на верхнем уровне вашего компонента, чтобы получить отложенную версию этого значения.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  // ...
}

См. дополнительные примеры ниже.

Параметры

  • value: Значение, которое вы хотите отложить. Оно может иметь любой тип.
  • Только Canary необязательно initialValue: Значение, используемое во время начальной отрисовки компонента. Если этот параметр опущен, useDeferredValue не будет откладывать отрисовку при начальном рендеринге, так как нет предыдущей версии value, которую можно использовать вместо неё.

Возвращаемое значение

  • currentValue: Во время начальной отрисовки возвращаемое отложенное значение будет таким же, как предоставленное вами значение. Во время обновлений React сначала попытается перерисовать с использованием старого значения (то есть вернёт старое значение), а затем попытается перерисовать в фоновом режиме с новым значением (то есть вернёт обновлённое значение).

Canary

В последних версиях React Canary, useDeferredValue возвращает initialValue при начальной отрисовке и планирует перерисовку в фоновом режиме с возвращённым value.

Ограничения

  • Когда обновление происходит внутри Transition, useDeferredValue всегда возвращает новое value и не запускает отложенную перерисовку, так как обновление уже отложено.

  • Значения, которые вы передаёте в useDeferredValue, должны быть либо примитивными значениями (такими как строки и числа), либо объектами, созданными вне отрисовки. Если вы создаёте новый объект во время отрисовки и сразу передаёте его в useDeferredValue, он будет отличаться на каждой отрисовке, вызывая ненужные перерисовки в фоновом режиме.

  • Когда useDeferredValue получает другое значение (в сравнении с Object.is), помимо текущей отрисовки (когда он всё ещё использует предыдущее значение), он планирует перерисовку в фоновом режиме с новым значением. Перерисовка в фоновом режиме прерывима: если есть другое обновление для value, React перезапустит перерисовку в фоновом режиме с нуля. Например, если пользователь вводит данные в инпут быстрее, чем диаграмма может перерисоваться с использованием отложенного значения, диаграмма перерисуется только после того, как пользователь перестанет вводить данные.

  • useDeferredValue интегрирован с <Suspense>. Если фоновое обновление, вызванное новым значением, приостановит пользовательский интерфейс, пользователь не увидит резервный вариант. Он будет видеть старое отложенное значение до загрузки данных.

  • useDeferredValue сам по себе не предотвращает дополнительные сетевые запросы.

  • Нет фиксированной задержки, вызванной самим useDeferredValue. Как только React завершит исходную перерисовку, он сразу же начнёт работу над перерисовкой в фоновом режиме с новым отложенным значением. Любые обновления, вызванные событиями (такими как ввод текста), прервут перерисовку в фоновом режиме и получат приоритет над ней.

  • Перерисовка в фоновом режиме, вызванная useDeferredValue, не запускает эффекты до тех пор, пока она не закреплена на экране. Если перерисовка в фоновом режиме приостановлена, её эффекты будут выполнены после загрузки данных и обновления пользовательского интерфейса.


Использование

Отображение устаревшего содержимого во время загрузки нового

Вызовите useDeferredValue на верхнем уровне вашего компонента, чтобы отложить обновление какой-либо части вашего пользовательского интерфейса.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  // ...
}

Во время начальной отрисовки отложенное значение будет таким же, как значение, которое вы предоставили.

Во время обновлений отложенное значение будет «отставать» от последнего значения. В частности, React сначала перерисует без обновления отложенного значения, а затем попытается перерисовать с новым полученным значением в фоновом режиме.

Давайте рассмотрим пример, чтобы увидеть, когда это полезно.

Примечание

Этот пример предполагает, что вы используете источник данных, поддерживающий Suspense:

  • Получение данных с использованием фреймворков, поддерживающих Suspense, таких как Relay и Next.js
  • Ленивая загрузка кода компонента с помощью lazy
  • Чтение значения Promise с помощью use

Узнайте больше о Suspense и его ограничениях.

В этом примере компонент SearchResults приостанавливается во время получения результатов поиска. Попробуйте набрать "a", подождать результатов, а затем изменить его на "ab". Результаты для "a" будут заменены резервным вариантом загрузки.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

Распространённый альтернативный шаблон пользовательского интерфейса — отложить обновление списка результатов и продолжать отображать предыдущие результаты до тех пор, пока новые результаты не будут готовы. Вызовите useDeferredValue для передачи отложенной версии запроса вниз:

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

query будет обновляться немедленно, поэтому инпут будет отображать новое значение. Однако deferredQuery сохранит своё предыдущее значение до загрузки данных, поэтому SearchResults отобразит устаревшие результаты в течение некоторого времени.

Введите "a" в примере ниже, подождите загрузки результатов, а затем измените ввод на "ab". Обратите внимание, что вместо резервного варианта Suspense вы сейчас видите устаревший список результатов до тех пор, пока новые результаты не будут загружены:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

Подробный разбор

Как работает отложенное значение под капотом?

Можно представить это в двух шагах:

  1. Сначала React перерисовывает с новым query ("ab") но со старым deferredQuery (ещё "a"). Значение deferredQuery, которое вы передаёте в список результатов, откладывается: оно «отстаёт» от значения query.

  2. В фоновом режиме React пытается перерисовать с обоими значениями query и deferredQuery обновлёнными до "ab". Если эта перерисовка завершится, React отобразит её на экране. Однако, если она приостановится (ещё не загружены результаты для "ab"), React откажется от этой попытки перерисовки и повторит её после загрузки данных. Пользователь продолжит видеть устаревшее отложенное значение до тех пор, пока данные не будут готовы.

Отложенная «фоновая» перерисовка прерывима. Например, если вы снова введёте данные в инпут, React откажется от неё и перезапустит с новым значением. React всегда будет использовать последнее предоставленное значение.

Обратите внимание, что всё равно есть сетевой запрос на каждый нажатие клавиши. То, что откладывается здесь — это отображение результатов (пока они не будут готовы), а не сами сетевые запросы. Даже если пользователь продолжает ввод, ответы на каждую нажатие клавиши кэшируются, поэтому нажатие Backspace происходит мгновенно и не вызывает повторного запроса.


Указание того, что содержимое устарело

В приведённом выше примере нет никаких указаний на то, что список результатов для последнего запроса всё ещё загружается. Это может быть непонятно пользователю, если новые результаты загружаются долго. Чтобы сделать более очевидным для пользователя, что список результатов не соответствует последнему запросу, можно добавить визуальное указание, когда отображается устаревший список результатов:

<div style={{
  opacity: query !== deferredQuery ? 0.5 : 1,
}}>
  <SearchResults query={deferredQuery} />
</div>

С этим изменением, как только вы начнете вводить, устаревший список результатов немного затемняется, пока не загрузится новый список результатов. Также можно добавить CSS-переход, чтобы задержка затемнения была плавной, как в примере ниже:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{
          opacity: isStale ? 0.5 : 1,
          transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
        }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}


Отсрочка перерисовки части пользовательского интерфейса

Вы также можете применить useDeferredValue в качестве оптимизации производительности. Это полезно, когда часть пользовательского интерфейса медленно перерисовывается, нет простого способа оптимизировать её, и вы хотите предотвратить блокировку остальной части пользовательского интерфейса.

Представьте, что у вас есть текстовое поле и компонент (например, диаграмма или длинный список), который перерисовывается при каждом нажатии клавиши:

function App() {
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={text} />
    </>
  );
}

Во-первых, оптимизируйте SlowList для пропуска перерисовки, когда его свойства одинаковые. Для этого оберните его в memo:

const SlowList = memo(function SlowList({ text }) {
  // ...
});

Однако это помогает только в том случае, если свойства SlowList одинаковы, как во время предыдущей отрисовки. Проблема, с которой вы сталкиваетесь сейчас, заключается в том, что она медленная, когда они разные, и когда вам нужно показать другой визуальный вывод.

Конкретно, основная проблема производительности заключается в том, что всякий раз, когда вы вводите данные в инпут, SlowList получает новые свойства, и перерисовка всего его дерева делает ввод неуклюжим. В этом случае useDeferredValue позволяет вам приоритизировать обновление инпута (который должен быть быстрым) над обновлением списка результатов (который может быть медленным):

function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

Это не ускоряет перерисовку SlowList. Однако это сообщает React, что перерисовка списка может быть понижена в приоритете, чтобы она не блокировала нажатия клавиш. Список «отстаёт» от инпута и затем «догоняет». Как и прежде, React попытается обновить список как можно скорее, но не будет блокировать пользователя при вводе.

Разница между useDeferredValue и неоптимизированной перерисовкой

Пример 1 из 2:
Отложенная перерисовка списка

В этом примере каждый элемент компонента SlowList искусственно замедлен, чтобы вы могли увидеть, как useDeferredValue позволяет сохранить инпут отзывчивым. Введите данные в инпут и обратите внимание, что ввод ощущается быстро, в то время как список «отстаёт» от него.

import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

Опасность

Для оптимизации требуется, чтобы SlowList было обернуто в memo. Это необходимо, так как всякий раз, когда text меняется, React должен быстро перерисовать родительский компонент. Во время этого перерисовки deferredText всё ещё имеет предыдущее значение, поэтому SlowList может пропустить перерисовку (его свойства не изменились). Без memo, ему пришлось бы перерисовываться в любом случае, что разрушает смысл оптимизации.

Подробное рассмотрение

Чем отличается отложенное значение от размытия и ограничения?

Существуют два распространённых метода оптимизации, которые вы могли использовать ранее в этой ситуации:

  • Размытие означает ожидание, пока пользователь перестанет печатать (например, на секунду), прежде чем обновлять список.
  • Ограничение означает обновление списка через некоторое время (например, не чаще, чем раз в секунду).

Хотя эти методы полезны в некоторых случаях, useDeferredValue лучше подходит для оптимизации отрисовки, потому что он глубоко интегрирован в сам React и адаптируется к устройству пользователя.

В отличие от размытия или ограничения, он не требует выбора фиксированной задержки. Если устройство пользователя быстрое (например, мощный ноутбук), отложенная перерисовка произойдёт почти мгновенно и не будет заметна. Если устройство пользователя медленное, список будет «отставать» от ввода пропорционально медленности устройства.

Также, в отличие от размытия или ограничения, отложенные перерисовки, выполняемые useDeferredValue по умолчанию, прерывисты. Это означает, что если React находится в середине перерисовки большого списка, но пользователь набирает ещё один символ, React отбросит эту перерисовку, обработает символ и затем снова начнёт отрисовку в фоновом режиме. В отличие от размытия и ограничения, которые всё равно создают неровный опыт, потому что они являются блокирующими: они просто откладывают момент, когда отрисовка блокирует ввод.

Если работа, которую вы оптимизируете, не происходит во время отрисовки, размытие и ограничение всё ещё полезны. Например, они могут позволить вам отправить меньше сетевых запросов. Вы также можете использовать эти методы вместе.

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://18.react.dev/reference/react/useDeferredValue

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API