Spec-Zone.ru › React

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: Значение, которое вы хотите отложить. Оно может быть любого типа.
  • необязательно initialValue: Значение, используемое во время первоначального рендеринга компонента. Если этот параметр опущен, useDeferredValue не будет отложен при первоначальном рендеринге, так как нет предыдущей версии value, которую можно отобразить вместо нее.

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

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

Ограничения

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

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

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

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

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

  • Нет фиксированной задержки, вызванной самим useDeferredValue. Как только React завершит первоначальный повторный рендеринг, 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://react.dev/reference/react/useDeferredValue

Spec-Zone.ru

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