useDeferredValue
useDeferredValue
é um Hook do React que permite adiar a atualização de uma parte da interface do usuário.
const deferredValue = useDeferredValue(value)
Referência
useDeferredValue(value, initialValue?)
Chame useDeferredValue
no nível mais alto do seu componente para obter uma versão adiada desse valor.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
Parâmetros
value
: O valor que você deseja adiar. Ele pode ter qualquer tipo.- Canary only opcional
initialValue
: Um valor a ser utilizado durante a renderização inicial de um componente. Se esta opção for omitida,useDeferredValue
não irá adiar durante a renderização inicial, pois não há uma versão anterior devalue
para renderizar em vez disso.
Retornos
currentValue
: Durante a renderização inicial, o valor adiado retornado será o mesmo que o valor fornecido. Durante as atualizações, o React primeiro tentará uma nova renderização com o valor antigo (portanto, retornará o valor antigo) e, em seguida, tentará outra renderização em segundo plano com o novo valor (portanto, retornará o valor atualizado).
Ressalvas
-
Quando uma atualização está dentro de uma Transição,
useDeferredValue
sempre retorna o novovalue
e não gera uma renderização adiada, já que a atualização já está adiada. -
Os valores que você passa para
useDeferredValue
devem ser valores primitivos (como strings e números) ou objetos criados fora da renderização. Se você criar um novo objeto durante a renderização e imediatamente passá-lo parauseDeferredValue
, ele será diferente a cada renderização, causando renderizações em segundo plano desnecessárias. -
Quando
useDeferredValue
recebe um valor diferente (comparado comObject.is
), além da renderização atual (quando ainda usa o valor anterior), ele agenda uma nova renderização em segundo plano com o novo valor. A nova renderização em segundo plano é interrompível: se houver outra atualização para ovalue
, o React reiniciará a nova renderização em segundo plano do zero. Por exemplo, se o usuário estiver digitando em um campo de entrada mais rápido do que um gráfico recebendo seu valor adiado pode renderizar, o gráfico só será re-renderizado após o usuário parar de digitar. -
useDeferredValue
é integrado com<Suspense>
. Se a atualização em segundo plano causada por um novo valor suspender a interface do usuário, o usuário não verá a opção de recuperação. Eles verão o antigo valor adiado até que os dados sejam carregados. -
useDeferredValue
não por si só impede requisições de rede extras. -
Não há um atraso fixo causado pelo próprio
useDeferredValue
. Assim que o React termina a renderização original, ele começará imediatamente a trabalhar na nova renderização em segundo plano com o novo valor adiado. Quaisquer atualizações causadas por eventos (como digitação) interromperão a nova renderização em segundo plano e terão prioridade sobre ela. -
A nova renderização em segundo plano causada por
useDeferredValue
não dispara Efeitos até que esteja comprometida na tela. Se a nova renderização em segundo plano for suspensa, seus Efeitos serão executados após os dados serem carregados e a interface do usuário for atualizada.
Uso
Mostrando conteúdo defasado enquanto o conteúdo fresco é carregado
Chame useDeferredValue
no nível mais alto do seu componente para adiar a atualização de alguma parte da sua interface.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
Durante a renderização inicial, o valor adiado será o mesmo que o valor que você forneceu.
Durante as atualizações, o valor adiado irá “atrasar-se” em relação ao valor mais recente. Em particular, o React primeiro re-renderizará sem atualizar o valor adiado e, em seguida, tentará re-renderizar com o novo valor recebido em segundo plano.
Vamos percorrer um exemplo para ver quando isso é útil.
Neste exemplo, o componente SearchResults
suspende enquanto busca os resultados da pesquisa. Tente digitar "a"
, aguarde os resultados e então edite para "ab"
. Os resultados para "a"
são substituídos pela opção de recuperação de carregamento.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Pesquisar álbuns: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Carregando...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
Um padrão comum de interface do usuário é adicionar a atualização da lista de resultados e continuar mostrando os resultados anteriores até que os novos resultados estejam prontos. Chame useDeferredValue
para passar uma versão adiada da consulta para baixo:
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Pesquisar álbuns:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Carregando...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
A query
será atualizada imediatamente, então o campo de entrada exibirá o novo valor. No entanto, o deferredQuery
manterá seu valor anterior até que os dados tenham sido carregados, de modo que SearchResults
mostrará os resultados desatualizados por um tempo.
Digite "a"
no exemplo abaixo, aguarde o carregamento dos resultados e, em seguida, edite a entrada para "ab"
. Observe como, ao invés da opção de recuperação do Suspense, você agora vê a lista de resultados desatualizada até que os novos resultados tenham sido carregados:
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> Pesquisar álbuns: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Carregando...</h2>}> <SearchResults query={deferredQuery} /> </Suspense> </> ); }
Deep Dive
Você pode pensar que isso acontece em duas etapas:
-
Primeiro, o React re-renderiza com a nova
query
("ab"
) mas com o antigodeferredQuery
(ainda"a"
). O valor dedeferredQuery
, que você passa para a lista de resultados, está adiado: ele “atrasou-se” em relação ao valor dequery
. -
Em segundo plano, o React tenta re-renderizar com os dois
query
edeferredQuery
atualizados para"ab"
. Se essa nova renderização for concluída, o React a mostrará na tela. No entanto, se ela suspender (os resultados para"ab"
ainda não foram carregados), o React abandonará essa tentativa de renderização e tentará mais uma vez após os dados terem sido carregados. O usuário continuará vendo o valor adiado desatualizado até que os dados estejam prontos.
A renderização “em segundo plano” adiada é interrompível. Por exemplo, se você digitar no campo de entrada novamente, o React abandonará essa renderização e reiniciará com o novo valor. O React sempre usará o valor mais recente fornecido.
Observe que ainda existe uma requisição de rede para cada tecla digitada. O que está sendo adiado aqui é a exibição de resultados (até que estejam prontos), não as requisições de rede em si. Mesmo se o usuário continuar digitando, as respostas para cada tecla digitada são armazenadas em cache, então pressionar Backspace é instantâneo e não busca novamente.
Indicando que o conteúdo está desatualizado
No exemplo acima, não há indicação de que a lista de resultados para a consulta mais recente ainda está carregando. Isso pode ser confuso para o usuário se os novos resultados demorarem a carregar. Para deixar mais óbvio para o usuário que a lista de resultados não corresponde à consulta mais recente, você pode adicionar uma indicação visual quando a lista de resultados desatualizada é exibida:
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
Com essa mudança, assim que você começar a digitar, a lista de resultados desatualizada será ligeiramente diminuída até que a nova lista de resultados carregue. Você também pode adicionar uma transição CSS para atrasar a diminuição de modo que se sinta gradual, como no exemplo abaixo:
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> Pesquisar álbuns: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Carregando...</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> </> ); }
Adiando re-renderizações para uma parte da interface do usuário
Você também pode aplicar useDeferredValue
como uma otimização de desempenho. Isso é útil quando uma parte da sua interface do usuário está lenta para re-renderizar, não há uma maneira fácil de otimizá-la e você deseja evitar que isso bloqueie o restante da interface.
Imagine que você tem um campo de texto e um componente (como um gráfico ou uma lista longa) que re-renderiza a cada tecla pressionada:
function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}
Primeiro, otimize SlowList
para pular a re-renderização quando suas props forem as mesmas. Para fazer isso, envolva-o em memo
:
const SlowList = memo(function SlowList({ text }) {
// ...
});
No entanto, isso só ajuda se as props do SlowList
forem as mesmas que durante a renderização anterior. O problema que você enfrenta agora é que ele é lento quando são diferentes, e quando você realmente precisa mostrar uma saída visual diferente.
Concretamente, o principal problema de desempenho é que, sempre que você digita no campo de entrada, o SlowList
recebe novas props e a re-renderização de toda a sua árvore faz com que a digitação pareça estranha. Nesse caso, useDeferredValue
permite que você priorize a atualização do campo de entrada (que deve ser rápida) em relação à atualização da lista de resultados (que pode ser mais lenta):
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}
Isso não torna a re-renderização do SlowList
mais rápida. No entanto, diz ao React que a re-renderização da lista pode ser despriorizada, para que isso não bloqueie as teclas digitadas. A lista irá “atrasar-se” em relação à entrada e depois “recuperar-se”. Como antes, o React tentará atualizar a lista o mais rápido possível, mas não bloqueará o usuário de digitar.
Example 1 of 2: Re-renderização adiada da lista
Neste exemplo, cada item no componente SlowList
é artificialmente desacelerado para que você possa ver como useDeferredValue
permite que você mantenha a entrada responsiva. Digite no campo de entrada e note que a digitação parece rápida enquanto a lista “atrasada” é exibida.
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} /> </> ); }
Deep Dive
Existem duas técnicas comuns de otimização que você pode ter usado antes neste cenário:
- Debouncing significa que você esperaria o usuário parar de digitar (por exemplo, por um segundo) antes de atualizar a lista.
- Throttling significa que você atualizaria a lista de vez em quando (por exemplo, no máximo uma vez por segundo).
Embora essas técnicas sejam úteis em alguns casos, useDeferredValue
é mais adequado para otimizar renderização porque está profundamente integrado com o próprio React e se adapta ao dispositivo do usuário.
Ao contrário de debouncing ou throttling, não requer que você escolha nenhum atraso fixo. Se o dispositivo do usuário for rápido (por exemplo, um laptop potente), a nova renderização adiada ocorreria quase imediatamente e não seria notável. Se o dispositivo do usuário for lento, a lista “atrasaria-se” em relação à entrada proporcionalmente à lentidão do dispositivo.
Além disso, ao contrário do debouncing ou throttling, as novas renderizações em segundo plano feitas por useDeferredValue
são interrompíveis por padrão. Isso significa que, se o React estiver no meio da re-renderização de uma lista grande, mas o usuário fizer outra tecla, o React abandonará essa re-renderização, lidará com a tecla e, em seguida, começará a renderizar em segundo plano novamente. Em contraste, debouncing e throttling ainda produzem uma experiência estranha porque são bloqueadores: eles apenas postergam o momento em que a renderização bloqueia a tecla.
Se o trabalho que você está otimizando não acontecer durante a renderização, debouncing e throttling ainda são úteis. Por exemplo, eles podem permitir que você faça menos requisições de rede. Você também pode usar essas técnicas juntas.